diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e7768..aae0181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### Version 1.0.21 Tag: v1.0.21 (2019-01-05) + * Added more video meta data + * Very basic like and dislike functionality + * UI changes to video detail page + * Torrent stream fatal fix (@lishoujun) + * AR Strings update (@rex07) + * ZH Strings update (@lishoujun) + * RU Strings update (@ferhadnecef) + * Refacturing (@lishoujun) + ### Version 1.0.20 Tag: v1.0.20 (2019-01-02) * Added basic login framework * AR Strings update (@rex07) diff --git a/app/build.gradle b/app/build.gradle index 7d6b93b..743bc74 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,9 +6,14 @@ android { applicationId "net.schueller.peertube" minSdkVersion 21 targetSdkVersion 28 - versionCode 1020 - versionName "1.0.20" + versionCode 1021 + versionName "1.0.21" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + ext { + libVersions = [ + exoplayer: '2.9.3' + ] + } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -41,13 +46,13 @@ android { // implementation "com.github.TorrentStream:TorrentStreamServer-Android:1.0.1" // implementation 'org.webrtc:google-webrtc:1.0.+' - // video player - implementation 'com.google.android.exoplayer:exoplayer-core:2.9.2' - implementation 'com.google.android.exoplayer:exoplayer-dash:2.9.2' - implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.2' - implementation 'com.google.android.exoplayer:exoplayer-hls:2.9.2' - implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.9.2' - implementation 'com.google.android.exoplayer:extension-mediasession:2.9.2' + // video player repo:jcenter() + implementation "com.google.android.exoplayer:exoplayer-core:$libVersions.exoplayer" + implementation "com.google.android.exoplayer:exoplayer-dash:$libVersions.exoplayer" + implementation "com.google.android.exoplayer:exoplayer-ui:$libVersions.exoplayer" + implementation "com.google.android.exoplayer:exoplayer-hls:$libVersions.exoplayer" + implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$libVersions.exoplayer" + implementation "com.google.android.exoplayer:extension-mediasession:$libVersions.exoplayer" // testing testImplementation 'junit:junit:4.12' diff --git a/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java b/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java new file mode 100644 index 0000000..6d01f9b --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/CommonActivity.java @@ -0,0 +1,32 @@ +package net.schueller.peertube.activity; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; + +import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; +import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; + +public class CommonActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Set Night Mode + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean("pref_dark_mode", false) ? + AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + + // Set theme + setTheme(getResources().getIdentifier( + sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), + "style", + getPackageName()) + ); + } + +} diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java index df82443..641757d 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.java @@ -71,7 +71,7 @@ import retrofit2.Response; import static net.schueller.peertube.helper.Constants.DEFAULT_THEME; import static net.schueller.peertube.helper.Constants.THEME_PREF_KEY; -public class VideoListActivity extends AppCompatActivity { +public class VideoListActivity extends CommonActivity { private String TAG = "VideoListActivity"; @@ -98,18 +98,6 @@ public class VideoListActivity extends AppCompatActivity { super.onCreate(savedInstanceState); - // Set Night Mode - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - AppCompatDelegate.setDefaultNightMode(sharedPref.getBoolean("pref_dark_mode", false) ? - AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); - - // Set theme - setTheme(getResources().getIdentifier( - sharedPref.getString(THEME_PREF_KEY, DEFAULT_THEME), - "style", - getPackageName()) - ); - setContentView(R.layout.activity_video_list); filter = null; diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java index b8f0699..66b0ed8 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java @@ -62,10 +62,12 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.mikepenz.iconics.Iconics; import com.squareup.picasso.Picasso; import net.schueller.peertube.R; +import net.schueller.peertube.fragment.VideoMetaDataFragment; import net.schueller.peertube.fragment.VideoOptionsFragment; import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.MetaDataHelper; import net.schueller.peertube.intents.Intents; +import net.schueller.peertube.model.Account; import net.schueller.peertube.model.Avatar; import net.schueller.peertube.model.Video; import net.schueller.peertube.network.GetVideoDataService; @@ -89,7 +91,7 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere private Context context = this; private TextView fullscreenButton; private Boolean isFullscreen = false; - + private TorrentStream torrentStream; boolean mBound = false; VideoPlayerService mService; @@ -182,8 +184,9 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere torrentStream.addListener(new TorrentListener() { @Override public void onStreamReady(Torrent torrent) { - Log.d(TAG, "Ready"); - mService.setCurrentStreamUrl(Uri.fromFile(torrent.getVideoFile()).toString()); + String videopath = Uri.fromFile(torrent.getVideoFile()).toString(); + Log.d(TAG, "Ready! torrentStream videopath:" + videopath); + mService.setCurrentStreamUrl(videopath); startPlayer(); } @@ -281,74 +284,23 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere // TODO: remove this code duplication, similar code as in video list rows - TextView videoName = findViewById(R.id.name); - TextView videoDescription = findViewById(R.id.description); - TextView videoOwner = findViewById(R.id.videoOwner); - TextView videoMeta = findViewById(R.id.videoMeta); - ImageView avatarView = findViewById(R.id.avatar); - TextView moreButton = findViewById(R.id.moreButton); - TextView videoOptions = findViewById(R.id.exo_more); - Video video = response.body(); mService.setCurrentVideo(video); - String baseUrl = APIUrlHelper.getUrl(context); - - Avatar avatar = video.getAccount().getAvatar(); - if (avatar != null) { - String avatarPath = avatar.getPath(); - Picasso.with(context) - .load(baseUrl + avatarPath) - .into(avatarView); + if (video == null){ + Toast.makeText(VideoPlayActivity.this, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show(); + return; } - videoName.setText(video.getName()); - videoMeta.setText( - MetaDataHelper.getMetaString( - video.getCreatedAt(), - video.getViews(), - getBaseContext() - ) - ); - videoOwner.setText( - MetaDataHelper.getOwnerString(video.getAccount().getName(), - video.getAccount().getHost(), - context - ) - ); - videoDescription.setText(video.getDescription()); + VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) + getSupportFragmentManager().findFragmentById(R.id.video_meta_data_fragment); - moreButton.setText(R.string.video_more_icon); - new Iconics.IconicsBuilder().ctx(context).on(moreButton).build(); + assert videoMetaDataFragment != null; + videoMetaDataFragment.updateVideoMeta(video, mService); - moreButton.setOnClickListener(v -> { - PopupMenu popup = new PopupMenu(context, v); - popup.setOnMenuItemClickListener(menuItem -> { - switch (menuItem.getItemId()) { - case R.id.menu_share: - Intents.Share(context, video); - return true; - default: - return false; - } - }); - popup.inflate(R.menu.menu_video_row_mode); - popup.show(); - }); - - // video player options - videoOptions.setText(R.string.video_more_icon); - new Iconics.IconicsBuilder().ctx(context).on(videoOptions).build(); - - videoOptions.setOnClickListener(v -> { - - VideoOptionsFragment videoOptionsFragment = - VideoOptionsFragment.newInstance(mService); - videoOptionsFragment.show(getSupportFragmentManager(), - "video_options_fragment"); - }); + Log.v(TAG, "url : " + video.getFiles().get(0).getFileUrl()); mService.setCurrentStreamUrl(video.getFiles().get(0).getFileUrl()); @@ -357,11 +309,13 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere if (sharedPref.getBoolean("pref_torrent_player", false)) { String stream = video.getFiles().get(0).getTorrentUrl(); - TorrentStream torrentStream = setupTorrentStream(); + Log.v(TAG, "getTorrentUrl : " + video.getFiles().get(0).getTorrentUrl()); + torrentStream = setupTorrentStream(); torrentStream.startStream(stream); } else { startPlayer(); } + Log.v(TAG,"end of load Video"); } @@ -412,8 +366,12 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere @Override protected void onDestroy() { - super.onDestroy(); simpleExoPlayerView.setPlayer(null); + if (torrentStream != null){ + torrentStream.stopStream(); + } + super.onDestroy(); + Log.v(TAG, "onDestroy..."); } @Override diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.java b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.java new file mode 100644 index 0000000..95507a6 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.java @@ -0,0 +1,278 @@ +package net.schueller.peertube.fragment; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.util.ArrayMap; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.mikepenz.fontawesome_typeface_library.FontAwesome; +import com.mikepenz.iconics.Iconics; +import com.mikepenz.iconics.IconicsDrawable; +import com.squareup.picasso.Picasso; + +import net.schueller.peertube.R; +import net.schueller.peertube.activity.VideoPlayActivity; +import net.schueller.peertube.helper.APIUrlHelper; +import net.schueller.peertube.helper.MetaDataHelper; +import net.schueller.peertube.intents.Intents; +import net.schueller.peertube.model.Account; +import net.schueller.peertube.model.Avatar; +import net.schueller.peertube.model.Video; +import net.schueller.peertube.network.GetVideoDataService; +import net.schueller.peertube.network.RetrofitInstance; +import net.schueller.peertube.network.Session; +import net.schueller.peertube.service.VideoPlayerService; + +import org.json.JSONObject; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.PopupMenu; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class VideoMetaDataFragment extends Fragment { + + private static final String TAG = "VideoMetaDataFragment"; + + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_video_meta, container, false); + } + + public void updateVideoMeta(Video video, VideoPlayerService mService) { + + Context context = getContext(); + Activity activity = getActivity(); + + + // Thumbs up + Button thumbsUpButton = activity.findViewById(R.id.video_thumbs_up); + thumbsUpButton.setText(R.string.video_thumbs_up_icon); + new Iconics.IconicsBuilder().ctx(context).on(thumbsUpButton).build(); + thumbsUpButton.setOnClickListener(v -> { + + if (Session.getInstance().isLoggedIn()) { + + // TODO: move this out helper/service + RequestBody body = RequestBody.create( + okhttp3.MediaType.parse("application/json"), + "{\"rating\":\"like\"}" + ); + + String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); + GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class); + + Call call = service.rateVideo(video.getId(), body); + + call.enqueue(new Callback() { + + @Override + public void onResponse(Call call, Response response) { + + Log.v(TAG, response.toString() ); + + // if 20x update likes + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(context, "Rating Failed", Toast.LENGTH_SHORT).show(); + } + }); + } else { + Toast.makeText(context, "You must login to use this service", Toast.LENGTH_SHORT).show(); + + } + + }); + + TextView thumbsUpButtonTotal = activity.findViewById(R.id.video_thumbs_up_total); + thumbsUpButtonTotal.setText(video.getLikes().toString()); + + // Thumbs Down + TextView thumbsDownButton = activity.findViewById(R.id.video_thumbs_down); + thumbsDownButton.setText(R.string.video_thumbs_down_icon); + new Iconics.IconicsBuilder().ctx(context).on(thumbsDownButton).build(); + thumbsDownButton.setOnClickListener(v -> { + + if (Session.getInstance().isLoggedIn()) { + + // TODO: move this out helper/service + RequestBody body = RequestBody.create( + okhttp3.MediaType.parse("application/json"), + "{\"rating\":\"dislike\"}" + ); + + String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); + GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class); + + Call call = service.rateVideo(video.getId(), body); + + call.enqueue(new Callback() { + + @Override + public void onResponse(Call call, Response response) { + + // if 20x update likes + + Log.v(TAG, response.toString() ); + + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(context, "Rating Failed", Toast.LENGTH_SHORT).show(); + } + }); + } else { + Toast.makeText(context, "You must login to use this service", Toast.LENGTH_SHORT).show(); + + } + + }); + + TextView thumbsDownButtonTotal = activity.findViewById(R.id.video_thumbs_down_total); + thumbsDownButtonTotal.setText(video.getDislikes().toString()); + + // Share + TextView videoShareButton = activity.findViewById(R.id.video_share); + videoShareButton.setText(R.string.video_share_icon); + new Iconics.IconicsBuilder().ctx(context).on(videoShareButton).build(); + videoShareButton.setOnClickListener(v -> Intents.Share(context, video)); + + // Download + TextView videoDownloadButton = activity.findViewById(R.id.video_download); + videoDownloadButton.setText(R.string.video_download_icon); + new Iconics.IconicsBuilder().ctx(context).on(videoDownloadButton).build(); + videoDownloadButton.setOnClickListener(v -> Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show()); + + // add to playlist + TextView videoSaveButton = activity.findViewById(R.id.video_save); + videoSaveButton.setText(R.string.video_save_icon); + new Iconics.IconicsBuilder().ctx(context).on(videoSaveButton).build(); + videoSaveButton.setOnClickListener(v -> Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show()); + + + Account account = video.getAccount(); + + // owner / creator Avatar + Avatar avatar = account.getAvatar(); + if (avatar != null) { + ImageView avatarView = activity.findViewById(R.id.avatar); + String baseUrl = APIUrlHelper.getUrl(context); + String avatarPath = avatar.getPath(); + Picasso.with(context) + .load(baseUrl + avatarPath) + .into(avatarView); + } + + + // title / name + TextView videoName = activity.findViewById(R.id.name); + videoName.setText(video.getName()); + + // created at / views + TextView videoMeta = activity.findViewById(R.id.videoMeta); + videoMeta.setText( + MetaDataHelper.getMetaString( + video.getCreatedAt(), + video.getViews(), + context + ) + ); + + // owner / creator + TextView videoOwner = activity.findViewById(R.id.videoOwner); + videoOwner.setText( + MetaDataHelper.getOwnerString(video.getAccount().getName(), + video.getAccount().getHost(), + context + ) + ); + + // description + TextView videoDescription = activity.findViewById(R.id.description); + videoDescription.setText(video.getDescription()); + + + // video privacy + TextView videoPrivacy = activity.findViewById(R.id.video_privacy); + videoPrivacy.setText(video.getPrivacy().getLabel()); + + // video category + TextView videoCategory = activity.findViewById(R.id.video_category); + videoCategory.setText(video.getCategory().getLabel()); + + // video privacy + TextView videoLicense = activity.findViewById(R.id.video_license); + videoLicense.setText(video.getLicence().getLabel()); + + // video langauge + TextView videoLanguage = activity.findViewById(R.id.video_language); + videoLanguage.setText(video.getLanguage().getLabel()); + + // video privacy + TextView videoTags = activity.findViewById(R.id.video_tags); + videoTags.setText(android.text.TextUtils.join(", ", video.getTags())); + + + // more button + TextView moreButton = activity.findViewById(R.id.moreButton); + moreButton.setText(R.string.video_more_icon); + new Iconics.IconicsBuilder().ctx(context).on(moreButton).build(); + + moreButton.setOnClickListener(v -> { + PopupMenu popup = new PopupMenu(context, v); + popup.setOnMenuItemClickListener(menuItem -> { + switch (menuItem.getItemId()) { + case R.id.video_more_report: + Log.v(TAG, "Report" ); + Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show(); + return true; + case R.id.video_more_blacklist: + Log.v(TAG, "Blacklist" ); + Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show(); + return true; + default: + return false; + } + }); + popup.inflate(R.menu.menu_video_more); + popup.show(); + }); + + // video player options + TextView videoOptions = activity.findViewById(R.id.exo_more); + videoOptions.setText(R.string.video_more_icon); + new Iconics.IconicsBuilder().ctx(context).on(videoOptions).build(); + + videoOptions.setOnClickListener(v -> { + + VideoOptionsFragment videoOptionsFragment = + VideoOptionsFragment.newInstance(mService); + videoOptionsFragment.show(getActivity().getSupportFragmentManager(), + "video_options_fragment"); + }); + + } + +} diff --git a/app/src/main/java/net/schueller/peertube/network/GetVideoDataService.java b/app/src/main/java/net/schueller/peertube/network/GetVideoDataService.java index 2159070..e88afbc 100644 --- a/app/src/main/java/net/schueller/peertube/network/GetVideoDataService.java +++ b/app/src/main/java/net/schueller/peertube/network/GetVideoDataService.java @@ -22,8 +22,13 @@ import net.schueller.peertube.model.VideoList; import java.util.Set; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Field; import retrofit2.http.GET; +import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; @@ -53,4 +58,11 @@ public interface GetVideoDataService { @Query("filter") String filter, @Query("languageOneOf") Set languages ); + + @PUT("videos/{id}/rate") + Call rateVideo( + @Path(value = "id", encoded = true) Integer id, + @Body RequestBody params + ); + } \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java b/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java index bc62ddb..b477948 100644 --- a/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java +++ b/app/src/main/java/net/schueller/peertube/service/VideoPlayerService.java @@ -35,6 +35,7 @@ import androidx.annotation.Nullable; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; +import android.widget.Toast; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -82,6 +83,8 @@ public class VideoPlayerService extends Service { @Override public void onCreate() { + Log.v(TAG, "onCreate..."); + super.onCreate(); player = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), new DefaultTrackSelector()); @@ -136,7 +139,11 @@ public class VideoPlayerService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { + Context context = this; Log.v(TAG, "onStartCommand..."); + if(currentStreamUrl == null){ + Toast.makeText(context, "currentStreamUrl must not null", Toast.LENGTH_SHORT).show(); + } playVideo(); return START_STICKY; } @@ -150,7 +157,7 @@ public class VideoPlayerService extends Service { public void setCurrentStreamUrl(String streamUrl) { - Log.v(TAG, "setCurrentStreamUrl..."); + Log.v(TAG, "setCurrentStreamUrl..." + streamUrl); currentStreamUrl = streamUrl; } diff --git a/app/src/main/res/layout/activity_video_play.xml b/app/src/main/res/layout/activity_video_play.xml index 2a2d293..efd7e37 100644 --- a/app/src/main/res/layout/activity_video_play.xml +++ b/app/src/main/res/layout/activity_video_play.xml @@ -18,8 +18,8 @@ android:layout_width="match_parent" android:layout_height="250dp" android:background="@color/videoBackgroundColor" - app:resize_mode="fixed_width" app:controller_layout_id="@layout/video_playback_controls" + app:resize_mode="fixed_width" /> @@ -32,89 +32,26 @@ android:indeterminate="false" android:max="100" /> + - - - - - - - - - - - + android:layout_height="match_parent"> + + - diff --git a/app/src/main/res/layout/fragment_video_meta.xml b/app/src/main/res/layout/fragment_video_meta.xml new file mode 100644 index 0000000..6032ba7 --- /dev/null +++ b/app/src/main/res/layout/fragment_video_meta.xml @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + + + +