Merge branch 'player-update' into 'develop'
Player update See merge request sschueller/peertube!46
This commit is contained in:
commit
91b4869934
@ -8,7 +8,7 @@ ENV ANDROID_SDK_CHECKSUM 124f2d5115eee365df6cf3228ffbca6fc3911d16f8025bebd5b1c6e
|
|||||||
# higher version casues Warning: Failed to find package
|
# higher version casues Warning: Failed to find package
|
||||||
ENV ANDROID_BUILD_TOOLS_VERSION 30.0.2
|
ENV ANDROID_BUILD_TOOLS_VERSION 30.0.2
|
||||||
ENV ANDROID_SDK_ROOT /usr/local/android-sdk-linux
|
ENV ANDROID_SDK_ROOT /usr/local/android-sdk-linux
|
||||||
ENV ANDROID_VERSION 30
|
ENV ANDROID_VERSION 32
|
||||||
# ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
# ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
||||||
ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin
|
ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin
|
||||||
|
|
||||||
|
@ -39,13 +39,13 @@ else {
|
|||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 32
|
||||||
buildToolsVersion "30.0.2"
|
buildToolsVersion "30.0.2"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "net.schueller.peertube"
|
applicationId "net.schueller.peertube"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 32
|
||||||
versionCode 1069
|
versionCode 1069
|
||||||
versionName "1.8.3"
|
versionName "1.8.3"
|
||||||
buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L'
|
buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L'
|
||||||
@ -94,10 +94,10 @@ android {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def room_version = "2.3.0"
|
def room_version = "2.4.0"
|
||||||
def lifecycleVersion = '2.3.1'
|
def lifecycleVersion = '2.4.0'
|
||||||
def exoplayer = '2.12.3'
|
def exoplayer = '2.16.1'
|
||||||
def fragment_version = "1.3.6"
|
def fragment_version = "1.4.0"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
@ -105,8 +105,8 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// Layouts and design
|
// Layouts and design
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||||
@ -118,7 +118,7 @@ dependencies {
|
|||||||
implementation 'com.mikepenz:fontawesome-typeface:5.9.0.2-kotlin@aar'
|
implementation 'com.mikepenz:fontawesome-typeface:5.9.0.2-kotlin@aar'
|
||||||
|
|
||||||
// http client / REST
|
// http client / REST
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
|
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
|
|
||||||
// image downloading and caching library
|
// image downloading and caching library
|
||||||
|
@ -41,7 +41,7 @@ import java.util.*
|
|||||||
|
|
||||||
class ServerAddressBookActivity : CommonActivity() {
|
class ServerAddressBookActivity : CommonActivity() {
|
||||||
|
|
||||||
private val TAG = "ServerAddressBookActivity"
|
private val TAG = "ServerAddBookAct"
|
||||||
|
|
||||||
private val mServerViewModel: ServerViewModel by viewModels()
|
private val mServerViewModel: ServerViewModel by viewModels()
|
||||||
private var addServerFragment: AddServerFragment? = null
|
private var addServerFragment: AddServerFragment? = null
|
||||||
@ -133,15 +133,15 @@ class ServerAddressBookActivity : CommonActivity() {
|
|||||||
AlertDialog.Builder(this@ServerAddressBookActivity)
|
AlertDialog.Builder(this@ServerAddressBookActivity)
|
||||||
.setTitle(getString(R.string.server_book_del_alert_title))
|
.setTitle(getString(R.string.server_book_del_alert_title))
|
||||||
.setMessage(getString(R.string.server_book_del_alert_msg))
|
.setMessage(getString(R.string.server_book_del_alert_msg))
|
||||||
.setPositiveButton(android.R.string.yes) { _: DialogInterface?, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
val position = viewHolder.adapterPosition
|
val position = viewHolder.bindingAdapterPosition
|
||||||
val server = adapter.getServerAtPosition(position)
|
val server = adapter.getServerAtPosition(position)
|
||||||
// Toast.makeText(ServerAddressBookActivity.this, "Deleting " +
|
// Toast.makeText(ServerAddressBookActivity.this, "Deleting " +
|
||||||
// server.getServerName(), Toast.LENGTH_LONG).show();
|
// server.getServerName(), Toast.LENGTH_LONG).show();
|
||||||
// Delete the server
|
// Delete the server
|
||||||
mServerViewModel.delete(server)
|
mServerViewModel.delete(server)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.adapterPosition) }
|
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) }
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package net.schueller.peertube.activity
|
|||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
import android.R.drawable
|
import android.R.drawable
|
||||||
import android.R.string
|
import android.R.string
|
||||||
|
import android.app.Activity
|
||||||
import android.app.AlertDialog.Builder
|
import android.app.AlertDialog.Builder
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -33,6 +34,7 @@ import android.view.MenuItem
|
|||||||
import android.view.MenuItem.OnActionExpandListener
|
import android.view.MenuItem.OnActionExpandListener
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.appcompat.widget.SearchView.OnSuggestionListener
|
import androidx.appcompat.widget.SearchView.OnSuggestionListener
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
@ -116,7 +118,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
Builder(this@VideoListActivity)
|
Builder(this@VideoListActivity)
|
||||||
.setTitle(getString(R.string.clear_search_history))
|
.setTitle(getString(R.string.clear_search_history))
|
||||||
.setMessage(getString(R.string.clear_search_history_prompt))
|
.setMessage(getString(R.string.clear_search_history_prompt))
|
||||||
.setPositiveButton(string.yes) { _, _ ->
|
.setPositiveButton(string.ok) { _, _ ->
|
||||||
val suggestions = SearchRecentSuggestions(
|
val suggestions = SearchRecentSuggestions(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
SearchSuggestionsProvider.AUTHORITY,
|
SearchSuggestionsProvider.AUTHORITY,
|
||||||
@ -124,7 +126,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
)
|
)
|
||||||
suggestions.clearHistory()
|
suggestions.clearHistory()
|
||||||
}
|
}
|
||||||
.setNegativeButton(string.no, null)
|
.setNegativeButton(string.cancel, null)
|
||||||
.setIcon(drawable.ic_dialog_alert)
|
.setIcon(drawable.ic_dialog_alert)
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
@ -160,8 +162,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
position
|
position
|
||||||
) as Cursor
|
) as Cursor
|
||||||
return cursor.getString(
|
return cursor.getString(
|
||||||
cursor
|
cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)
|
||||||
.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,15 +179,26 @@ class VideoListActivity : CommonActivity() {
|
|||||||
stopService(Intent(this, VideoPlayerService::class.java))
|
stopService(Intent(this, VideoPlayerService::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
// public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
// super.onActivityResult(requestCode, resultCode, data)
|
||||||
if (requestCode == SWITCH_INSTANCE) {
|
// if (requestCode == SWITCH_INSTANCE) {
|
||||||
if (resultCode == RESULT_OK) {
|
// if (resultCode == RESULT_OK) {
|
||||||
loadVideos(currentStart, count, sort, filter)
|
// loadVideos(currentStart, count, sort, filter)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
private var resultLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
loadVideos(currentStart, count, sort, filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openActivityForResult(intent: Intent) {
|
||||||
|
resultLauncher.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
// Handle action bar item clicks here. The action bar will
|
// Handle action bar item clicks here. The action bar will
|
||||||
// automatically handle clicks on the Home/Up button, so long
|
// automatically handle clicks on the Home/Up button, so long
|
||||||
@ -213,7 +225,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
}
|
}
|
||||||
id.action_server_address_book -> {
|
id.action_server_address_book -> {
|
||||||
val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java)
|
val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java)
|
||||||
this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE)
|
openActivityForResult(addressBookActivityIntent)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@ -461,7 +473,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
// new IconicsDrawable(this, FontAwesome.Icon.faw_user_circle));
|
// new IconicsDrawable(this, FontAwesome.Icon.faw_user_circle));
|
||||||
|
|
||||||
// Click Listener
|
// Click Listener
|
||||||
navigation.setOnNavigationItemSelectedListener { menuItem: MenuItem ->
|
navigation.setOnItemSelectedListener { menuItem: MenuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
id.navigation_overview -> {
|
id.navigation_overview -> {
|
||||||
// TODO
|
// TODO
|
||||||
@ -470,7 +482,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
loadOverview(currentPage)
|
loadOverview(currentPage)
|
||||||
overViewActive = true
|
overViewActive = true
|
||||||
}
|
}
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnItemSelectedListener true
|
||||||
}
|
}
|
||||||
id.navigation_trending -> {
|
id.navigation_trending -> {
|
||||||
//Log.v(TAG, "navigation_trending");
|
//Log.v(TAG, "navigation_trending");
|
||||||
@ -482,7 +494,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
subscriptions = false
|
subscriptions = false
|
||||||
loadVideos(currentStart, count, sort, filter)
|
loadVideos(currentStart, count, sort, filter)
|
||||||
}
|
}
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnItemSelectedListener true
|
||||||
}
|
}
|
||||||
id.navigation_recent -> {
|
id.navigation_recent -> {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
@ -493,7 +505,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
subscriptions = false
|
subscriptions = false
|
||||||
loadVideos(currentStart, count, sort, filter)
|
loadVideos(currentStart, count, sort, filter)
|
||||||
}
|
}
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnItemSelectedListener true
|
||||||
}
|
}
|
||||||
id.navigation_local -> {
|
id.navigation_local -> {
|
||||||
//Log.v(TAG, "navigation_trending");
|
//Log.v(TAG, "navigation_trending");
|
||||||
@ -505,15 +517,15 @@ class VideoListActivity : CommonActivity() {
|
|||||||
subscriptions = false
|
subscriptions = false
|
||||||
loadVideos(currentStart, count, sort, filter)
|
loadVideos(currentStart, count, sort, filter)
|
||||||
}
|
}
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnItemSelectedListener true
|
||||||
}
|
}
|
||||||
id.navigation_subscriptions -> //Log.v(TAG, "navigation_subscriptions");
|
id.navigation_subscriptions -> //Log.v(TAG, "navigation_subscriptions");
|
||||||
if (!Session.getInstance().isLoggedIn) {
|
if (!Session.getInstance().isLoggedIn) {
|
||||||
// Intent intent = new Intent(this, LoginActivity.class);
|
// Intent intent = new Intent(this, LoginActivity.class);
|
||||||
// this.startActivity(intent);
|
// this.startActivity(intent);
|
||||||
val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java)
|
val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java)
|
||||||
this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE)
|
openActivityForResult(addressBookActivityIntent)
|
||||||
return@setOnNavigationItemSelectedListener false
|
return@setOnItemSelectedListener false
|
||||||
} else {
|
} else {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
overViewActive = false
|
overViewActive = false
|
||||||
@ -523,7 +535,7 @@ class VideoListActivity : CommonActivity() {
|
|||||||
subscriptions = true
|
subscriptions = true
|
||||||
loadVideos(currentStart, count, sort, filter)
|
loadVideos(currentStart, count, sort, filter)
|
||||||
}
|
}
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnItemSelectedListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@ -574,6 +586,5 @@ class VideoListActivity : CommonActivity() {
|
|||||||
|
|
||||||
const val EXTRA_VIDEOID = "VIDEOID"
|
const val EXTRA_VIDEOID = "VIDEOID"
|
||||||
const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST"
|
const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST"
|
||||||
const val SWITCH_INSTANCE = 2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,502 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.schueller.peertube.activity;
|
|
||||||
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.AppOpsManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.PictureInPictureParams;
|
|
||||||
import android.app.RemoteAction;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.graphics.drawable.Icon;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Rational;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import net.schueller.peertube.R;
|
|
||||||
import net.schueller.peertube.fragment.VideoMetaDataFragment;
|
|
||||||
import net.schueller.peertube.fragment.VideoPlayerFragment;
|
|
||||||
import net.schueller.peertube.service.VideoPlayerService;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
|
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PAUSE;
|
|
||||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PLAY;
|
|
||||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
|
|
||||||
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
|
|
||||||
|
|
||||||
public class VideoPlayActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private static final String TAG = "VideoPlayActivity";
|
|
||||||
|
|
||||||
static boolean floatMode = false;
|
|
||||||
|
|
||||||
private static final int REQUEST_CODE = 101;
|
|
||||||
private BroadcastReceiver receiver;
|
|
||||||
|
|
||||||
//This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode.
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
public void makePipControls() {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
ArrayList<RemoteAction> actions = new ArrayList<>();
|
|
||||||
|
|
||||||
Intent actionIntent = new Intent(getString(R.string.app_background_audio));
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
|
||||||
@SuppressLint({"NewApi", "LocalSuppress"}) Icon icon = Icon.createWithResource(getApplicationContext(), android.R.drawable.stat_sys_speakerphone);
|
|
||||||
@SuppressLint({"NewApi", "LocalSuppress"}) RemoteAction remoteAction = new RemoteAction(icon, "close pip", "from pip window custom command", pendingIntent);
|
|
||||||
actions.add(remoteAction);
|
|
||||||
|
|
||||||
actionIntent = new Intent(ACTION_STOP);
|
|
||||||
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
|
||||||
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop);
|
|
||||||
remoteAction = new RemoteAction(icon, "play", "stop the media", pendingIntent);
|
|
||||||
actions.add(remoteAction);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
if (videoPlayerFragment.isPaused()) {
|
|
||||||
Log.e(TAG, "setting actions with play button");
|
|
||||||
actionIntent = new Intent(ACTION_PLAY);
|
|
||||||
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
|
||||||
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_play);
|
|
||||||
remoteAction = new RemoteAction(icon, "play", "play the media", pendingIntent);
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "setting actions with pause button");
|
|
||||||
actionIntent = new Intent(ACTION_PAUSE);
|
|
||||||
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0);
|
|
||||||
icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause);
|
|
||||||
remoteAction = new RemoteAction(icon, "pause", "pause the media", pendingIntent);
|
|
||||||
}
|
|
||||||
actions.add(remoteAction);
|
|
||||||
|
|
||||||
|
|
||||||
//add custom actions to pip window
|
|
||||||
PictureInPictureParams params =
|
|
||||||
new PictureInPictureParams.Builder()
|
|
||||||
.setActions(actions)
|
|
||||||
.build();
|
|
||||||
setPictureInPictureParams(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void changedToPipMode() {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
videoPlayerFragment.showControls(false);
|
|
||||||
//create custom actions
|
|
||||||
makePipControls();
|
|
||||||
|
|
||||||
//setup receiver to handle customer actions
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(ACTION_STOP);
|
|
||||||
filter.addAction(ACTION_PAUSE);
|
|
||||||
filter.addAction(ACTION_PLAY);
|
|
||||||
filter.addAction((getString(R.string.app_background_audio)));
|
|
||||||
receiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String action = intent.getAction();
|
|
||||||
assert action != null;
|
|
||||||
if (action.equals(ACTION_PAUSE)) {
|
|
||||||
videoPlayerFragment.pauseVideo();
|
|
||||||
makePipControls();
|
|
||||||
}
|
|
||||||
if (action.equals(ACTION_PLAY)) {
|
|
||||||
videoPlayerFragment.unPauseVideo();
|
|
||||||
makePipControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.equals(getString(R.string.app_background_audio))) {
|
|
||||||
unregisterReceiver(receiver);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
if (action.equals(ACTION_STOP)) {
|
|
||||||
unregisterReceiver(receiver);
|
|
||||||
finishAndRemoveTask();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
registerReceiver(receiver, filter);
|
|
||||||
|
|
||||||
Log.v(TAG, "switched to pip ");
|
|
||||||
floatMode = true;
|
|
||||||
videoPlayerFragment.showControls(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void changedToNormalMode() {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
videoPlayerFragment.showControls(true);
|
|
||||||
if (receiver != null) {
|
|
||||||
unregisterReceiver(receiver);
|
|
||||||
}
|
|
||||||
Log.v(TAG, "switched to normal");
|
|
||||||
floatMode = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
// Set theme
|
|
||||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
setTheme(getResources().getIdentifier(
|
|
||||||
sharedPref.getString(
|
|
||||||
getString(R.string.pref_theme_key),
|
|
||||||
getString(R.string.app_default_theme)
|
|
||||||
),
|
|
||||||
"style",
|
|
||||||
getPackageName())
|
|
||||||
);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_video_play);
|
|
||||||
|
|
||||||
// get video ID
|
|
||||||
Intent intent = getIntent();
|
|
||||||
String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
|
||||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
String playingVideo = videoPlayerFragment.getVideoUuid();
|
|
||||||
Log.v(TAG, "oncreate click: " + videoUuid + " is trying to replace: " + playingVideo);
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(playingVideo)) {
|
|
||||||
Log.v(TAG, "oncreate no video currently playing");
|
|
||||||
videoPlayerFragment.start(videoUuid);
|
|
||||||
} else if (!playingVideo.equals(videoUuid)) {
|
|
||||||
Log.v(TAG, "oncreate different video playing currently");
|
|
||||||
videoPlayerFragment.stopVideo();
|
|
||||||
videoPlayerFragment.start(videoUuid);
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "oncreate same video playing currently");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are in landscape set the video to fullscreen
|
|
||||||
int orientation = this.getResources().getConfiguration().orientation;
|
|
||||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
||||||
setOrientation(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNewIntent(Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
setIntent(intent);
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
|
||||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID);
|
|
||||||
Log.v(TAG, "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.getVideoUuid());
|
|
||||||
String playingVideo = videoPlayerFragment.getVideoUuid();
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(playingVideo)) {
|
|
||||||
Log.v(TAG, "new intent no video currently playing");
|
|
||||||
videoPlayerFragment.start(videoUuid);
|
|
||||||
} else if (!playingVideo.equals(videoUuid)) {
|
|
||||||
Log.v(TAG, "new intent different video playing currently");
|
|
||||||
videoPlayerFragment.stopVideo();
|
|
||||||
videoPlayerFragment.start(videoUuid);
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "new intent same video playing currently");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are in landscape set the video to fullscreen
|
|
||||||
int orientation = this.getResources().getConfiguration().orientation;
|
|
||||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
||||||
setOrientation(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
|
||||||
Log.v(TAG, "onConfigurationChanged()...");
|
|
||||||
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
|
|
||||||
// Checking the orientation changes of the screen
|
|
||||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
||||||
setOrientation(true);
|
|
||||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
||||||
setOrientation(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setOrientation(Boolean isLandscape) {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
|
||||||
VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) videoPlayerFragment.requireView().getLayoutParams();
|
|
||||||
params.width = FrameLayout.LayoutParams.MATCH_PARENT;
|
|
||||||
params.height = isLandscape ? FrameLayout.LayoutParams.MATCH_PARENT : (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, getResources().getDisplayMetrics());
|
|
||||||
|
|
||||||
videoPlayerFragment.requireView().setLayoutParams(params);
|
|
||||||
|
|
||||||
if (videoMetaFragment != null) {
|
|
||||||
FragmentTransaction transaction = fragmentManager.beginTransaction()
|
|
||||||
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
|
|
||||||
|
|
||||||
if (isLandscape) {
|
|
||||||
transaction.hide(videoMetaFragment);
|
|
||||||
} else {
|
|
||||||
transaction.show(videoMetaFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
videoPlayerFragment.setIsFullscreen(isLandscape);
|
|
||||||
|
|
||||||
if ( isLandscape ) {
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
} else {
|
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
|
||||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
videoPlayerFragment.destroyVideo();
|
|
||||||
|
|
||||||
super.onDestroy();
|
|
||||||
Log.v(TAG, "onDestroy...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
Log.v(TAG, "onPause()...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
Log.v(TAG, "onResume()...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
|
||||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
videoPlayerFragment.stopVideo();
|
|
||||||
|
|
||||||
Log.v(TAG, "onStop()...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
Log.v(TAG, "onStart()...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
@Override
|
|
||||||
public void onUserLeaveHint() {
|
|
||||||
|
|
||||||
Log.v(TAG, "onUserLeaveHint()...");
|
|
||||||
|
|
||||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
|
||||||
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment);
|
|
||||||
|
|
||||||
String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
assert backgroundBehavior != null;
|
|
||||||
if ( videoMetaDataFragment.isLeaveAppExpected() )
|
|
||||||
{
|
|
||||||
super.onUserLeaveHint();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
|
|
||||||
Log.v(TAG, "stop the video");
|
|
||||||
|
|
||||||
videoPlayerFragment.pauseVideo();
|
|
||||||
stopService(new Intent(this, VideoPlayerService.class));
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
|
|
||||||
Log.v(TAG, "play the Audio");
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
|
|
||||||
Log.v(TAG, "play in floating video");
|
|
||||||
//canEnterPIPMode makes sure API level is high enough
|
|
||||||
if (canEnterPipMode(this)) {
|
|
||||||
Log.v(TAG, "enabling pip");
|
|
||||||
enterPipMode();
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "unable to use pip");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Deal with bad entries from older version
|
|
||||||
Log.v(TAG, "No setting, fallback");
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// @RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
public void onBackPressed() {
|
|
||||||
|
|
||||||
Log.v(TAG, "onBackPressed()...");
|
|
||||||
|
|
||||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment)
|
|
||||||
getSupportFragmentManager().findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
assert videoPlayerFragment != null;
|
|
||||||
|
|
||||||
// copying Youtube behavior to have back button exit full screen.
|
|
||||||
if (videoPlayerFragment.getIsFullscreen()) {
|
|
||||||
Log.v(TAG, "exiting full screen");
|
|
||||||
videoPlayerFragment.fullScreenToggle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// pause video if pref is enabled
|
|
||||||
if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) {
|
|
||||||
videoPlayerFragment.pauseVideo();
|
|
||||||
}
|
|
||||||
|
|
||||||
String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key));
|
|
||||||
|
|
||||||
assert backgroundBehavior != null;
|
|
||||||
|
|
||||||
if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) {
|
|
||||||
Log.v(TAG, "stop the video");
|
|
||||||
videoPlayerFragment.pauseVideo();
|
|
||||||
stopService(new Intent(this, VideoPlayerService.class));
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) {
|
|
||||||
Log.v(TAG, "play the Audio");
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
} else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) {
|
|
||||||
Log.v(TAG, "play in floating video");
|
|
||||||
//canEnterPIPMode makes sure API level is high enough
|
|
||||||
if (canEnterPipMode(this)) {
|
|
||||||
Log.v(TAG, "enabling pip");
|
|
||||||
enterPipMode();
|
|
||||||
//fixes problem where back press doesn't bring up video list after returning from PIP mode
|
|
||||||
Intent intentSettings = new Intent(this, VideoListActivity.class);
|
|
||||||
this.startActivity(intentSettings);
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "Unable to enter PIP mode");
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Deal with bad entries from older version
|
|
||||||
Log.v(TAG, "No setting, fallback");
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public void enterPipMode() {
|
|
||||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
final VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById( R.id.video_player_fragment );
|
|
||||||
|
|
||||||
if ( videoPlayerFragment.getVideoAspectRatio() == 0 ) {
|
|
||||||
Log.i( TAG, "impossible to switch to pip" );
|
|
||||||
} else {
|
|
||||||
Rational rational = new Rational( (int) ( videoPlayerFragment.getVideoAspectRatio() * 100 ), 100 );
|
|
||||||
PictureInPictureParams mParams =
|
|
||||||
new PictureInPictureParams.Builder()
|
|
||||||
.setAspectRatio( rational )
|
|
||||||
// .setSourceRectHint(new Rect(0,500,400,600))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
enterPictureInPictureMode( mParams );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment);
|
|
||||||
|
|
||||||
if (videoPlayerFragment != null) {
|
|
||||||
|
|
||||||
if (isInPictureInPictureMode) {
|
|
||||||
changedToPipMode();
|
|
||||||
Log.v(TAG, "switched to pip ");
|
|
||||||
videoPlayerFragment.useController(false);
|
|
||||||
} else {
|
|
||||||
changedToNormalMode();
|
|
||||||
Log.v(TAG, "switched to normal");
|
|
||||||
videoPlayerFragment.useController(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "videoPlayerFragment is NULL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,461 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.activity
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import net.schueller.peertube.fragment.VideoPlayerFragment
|
||||||
|
import net.schueller.peertube.R
|
||||||
|
import android.app.RemoteAction
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||||
|
import android.app.PictureInPictureParams
|
||||||
|
import android.content.*
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.TextUtils
|
||||||
|
import net.schueller.peertube.fragment.VideoMetaDataFragment
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.WindowManager
|
||||||
|
import net.schueller.peertube.service.VideoPlayerService
|
||||||
|
import net.schueller.peertube.helper.VideoHelper
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Rational
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import net.schueller.peertube.fragment.VideoDescriptionFragment
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
class VideoPlayActivity : CommonActivity() {
|
||||||
|
private var receiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
|
//This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode.
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
fun makePipControls() {
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||||
|
val actions = ArrayList<RemoteAction>()
|
||||||
|
var actionIntent = Intent(getString(R.string.app_background_audio))
|
||||||
|
var pendingIntent =
|
||||||
|
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0)
|
||||||
|
@SuppressLint("NewApi", "LocalSuppress") var icon = Icon.createWithResource(
|
||||||
|
applicationContext, android.R.drawable.stat_sys_speakerphone
|
||||||
|
)
|
||||||
|
@SuppressLint("NewApi", "LocalSuppress") var remoteAction =
|
||||||
|
RemoteAction(icon!!, "close pip", "from pip window custom command", pendingIntent!!)
|
||||||
|
actions.add(remoteAction)
|
||||||
|
actionIntent = Intent(PlayerNotificationManager.ACTION_STOP)
|
||||||
|
pendingIntent =
|
||||||
|
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0)
|
||||||
|
icon = Icon.createWithResource(
|
||||||
|
applicationContext,
|
||||||
|
com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop
|
||||||
|
)
|
||||||
|
remoteAction = RemoteAction(icon, "play", "stop the media", pendingIntent)
|
||||||
|
actions.add(remoteAction)
|
||||||
|
assert(videoPlayerFragment != null)
|
||||||
|
if (videoPlayerFragment!!.isPaused) {
|
||||||
|
Log.e(TAG, "setting actions with play button")
|
||||||
|
actionIntent = Intent(PlayerNotificationManager.ACTION_PLAY)
|
||||||
|
pendingIntent =
|
||||||
|
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0)
|
||||||
|
icon = Icon.createWithResource(
|
||||||
|
applicationContext,
|
||||||
|
com.google.android.exoplayer2.ui.R.drawable.exo_notification_play
|
||||||
|
)
|
||||||
|
remoteAction = RemoteAction(icon, "play", "play the media", pendingIntent)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "setting actions with pause button")
|
||||||
|
actionIntent = Intent(PlayerNotificationManager.ACTION_PAUSE)
|
||||||
|
pendingIntent =
|
||||||
|
PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0)
|
||||||
|
icon = Icon.createWithResource(
|
||||||
|
applicationContext,
|
||||||
|
com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause
|
||||||
|
)
|
||||||
|
remoteAction = RemoteAction(icon, "pause", "pause the media", pendingIntent)
|
||||||
|
}
|
||||||
|
actions.add(remoteAction)
|
||||||
|
|
||||||
|
|
||||||
|
//add custom actions to pip window
|
||||||
|
val params = PictureInPictureParams.Builder()
|
||||||
|
.setActions(actions)
|
||||||
|
.build()
|
||||||
|
setPictureInPictureParams(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changedToPipMode() {
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
videoPlayerFragment.showControls(false)
|
||||||
|
//create custom actions
|
||||||
|
makePipControls()
|
||||||
|
|
||||||
|
//setup receiver to handle customer actions
|
||||||
|
val filter = IntentFilter()
|
||||||
|
filter.addAction(PlayerNotificationManager.ACTION_STOP)
|
||||||
|
filter.addAction(PlayerNotificationManager.ACTION_PAUSE)
|
||||||
|
filter.addAction(PlayerNotificationManager.ACTION_PLAY)
|
||||||
|
filter.addAction(getString(R.string.app_background_audio))
|
||||||
|
receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val action = intent.action!!
|
||||||
|
if (action == PlayerNotificationManager.ACTION_PAUSE) {
|
||||||
|
videoPlayerFragment.pauseVideo()
|
||||||
|
makePipControls()
|
||||||
|
}
|
||||||
|
if (action == PlayerNotificationManager.ACTION_PLAY) {
|
||||||
|
videoPlayerFragment.unPauseVideo()
|
||||||
|
makePipControls()
|
||||||
|
}
|
||||||
|
if (action == getString(R.string.app_background_audio)) {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
if (action == PlayerNotificationManager.ACTION_STOP) {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
finishAndRemoveTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerReceiver(receiver, filter)
|
||||||
|
Log.v(TAG, "switched to pip ")
|
||||||
|
floatMode = true
|
||||||
|
videoPlayerFragment.showControls(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changedToNormalMode() {
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
videoPlayerFragment.showControls(true)
|
||||||
|
if (receiver != null) {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
}
|
||||||
|
Log.v(TAG, "switched to normal")
|
||||||
|
floatMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// Set theme
|
||||||
|
val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE)
|
||||||
|
setTheme(
|
||||||
|
resources.getIdentifier(
|
||||||
|
sharedPref.getString(
|
||||||
|
getString(R.string.pref_theme_key),
|
||||||
|
getString(R.string.app_default_theme)
|
||||||
|
),
|
||||||
|
"style",
|
||||||
|
packageName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setContentView(R.layout.activity_video_play)
|
||||||
|
|
||||||
|
// get video ID
|
||||||
|
val intent = intent
|
||||||
|
val videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID)
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
val playingVideo = videoPlayerFragment.videoUuid
|
||||||
|
Log.v(TAG, "oncreate click: $videoUuid is trying to replace: $playingVideo")
|
||||||
|
when {
|
||||||
|
TextUtils.isEmpty(playingVideo) -> {
|
||||||
|
Log.v(TAG, "oncreate no video currently playing")
|
||||||
|
videoPlayerFragment.start(videoUuid)
|
||||||
|
}
|
||||||
|
playingVideo != videoUuid -> {
|
||||||
|
Log.v(TAG, "oncreate different video playing currently")
|
||||||
|
videoPlayerFragment.stopVideo()
|
||||||
|
videoPlayerFragment.start(videoUuid)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.v(TAG, "oncreate same video playing currently")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are in landscape set the video to fullscreen
|
||||||
|
val orientation = this.resources.configuration.orientation
|
||||||
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
setOrientation(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
val videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID)
|
||||||
|
Log.v(
|
||||||
|
TAG,
|
||||||
|
"new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.videoUuid
|
||||||
|
)
|
||||||
|
val playingVideo = videoPlayerFragment.videoUuid
|
||||||
|
when {
|
||||||
|
TextUtils.isEmpty(playingVideo) -> {
|
||||||
|
Log.v(TAG, "new intent no video currently playing")
|
||||||
|
videoPlayerFragment.start(videoUuid)
|
||||||
|
}
|
||||||
|
playingVideo != videoUuid -> {
|
||||||
|
Log.v(TAG, "new intent different video playing currently")
|
||||||
|
videoPlayerFragment.stopVideo()
|
||||||
|
videoPlayerFragment.start(videoUuid)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.v(TAG, "new intent same video playing currently")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are in landscape set the video to fullscreen
|
||||||
|
val orientation = this.resources.configuration.orientation
|
||||||
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
setOrientation(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
Log.v(TAG, "onConfigurationChanged()...")
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
|
||||||
|
// Checking the orientation changes of the screen
|
||||||
|
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
setOrientation(true)
|
||||||
|
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
setOrientation(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setOrientation(isLandscape: Boolean) {
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||||
|
val videoMetaFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment?
|
||||||
|
assert(videoPlayerFragment != null)
|
||||||
|
val params = videoPlayerFragment!!.requireView().layoutParams as RelativeLayout.LayoutParams
|
||||||
|
params.width = FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
params.height =
|
||||||
|
if (isLandscape) FrameLayout.LayoutParams.MATCH_PARENT else TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
250f,
|
||||||
|
resources.displayMetrics
|
||||||
|
)
|
||||||
|
.toInt()
|
||||||
|
videoPlayerFragment.requireView().layoutParams = params
|
||||||
|
if (videoMetaFragment != null) {
|
||||||
|
val transaction = fragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
if (isLandscape) {
|
||||||
|
transaction.hide(videoMetaFragment)
|
||||||
|
} else {
|
||||||
|
transaction.show(videoMetaFragment)
|
||||||
|
}
|
||||||
|
transaction.commit()
|
||||||
|
}
|
||||||
|
videoPlayerFragment.setIsFullscreen(isLandscape)
|
||||||
|
if (isLandscape) {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
} else {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
videoPlayerFragment.destroyVideo()
|
||||||
|
super.onDestroy()
|
||||||
|
Log.v(TAG, "onDestroy...")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
Log.v(TAG, "onPause()...")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.v(TAG, "onResume()...")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
videoPlayerFragment.stopVideo()
|
||||||
|
|
||||||
|
// TODO: doesn't remove fragment??
|
||||||
|
val fragment: Fragment? = supportFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG)
|
||||||
|
if (fragment != null) {
|
||||||
|
Log.v(TAG, "remove VideoDescriptionFragment")
|
||||||
|
supportFragmentManager.beginTransaction().remove(fragment).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "onStop()...")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
Log.v(TAG, "onStart()...")
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public override fun onUserLeaveHint() {
|
||||||
|
Log.v(TAG, "onUserLeaveHint()...")
|
||||||
|
val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE)
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||||
|
val videoMetaDataFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment?
|
||||||
|
val backgroundBehavior = sharedPref.getString(
|
||||||
|
getString(R.string.pref_background_behavior_key),
|
||||||
|
getString(R.string.pref_background_stop_key)
|
||||||
|
)
|
||||||
|
assert(videoPlayerFragment != null)
|
||||||
|
assert(backgroundBehavior != null)
|
||||||
|
if (videoMetaDataFragment!!.isLeaveAppExpected) {
|
||||||
|
super.onUserLeaveHint()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (backgroundBehavior == getString(R.string.pref_background_stop_key)) {
|
||||||
|
Log.v(TAG, "stop the video")
|
||||||
|
videoPlayerFragment!!.pauseVideo()
|
||||||
|
stopService(Intent(this, VideoPlayerService::class.java))
|
||||||
|
super.onBackPressed()
|
||||||
|
} else if (backgroundBehavior == getString(R.string.pref_background_audio_key)) {
|
||||||
|
Log.v(TAG, "play the Audio")
|
||||||
|
super.onBackPressed()
|
||||||
|
} else if (backgroundBehavior == getString(R.string.pref_background_float_key)) {
|
||||||
|
Log.v(TAG, "play in floating video")
|
||||||
|
//canEnterPIPMode makes sure API level is high enough
|
||||||
|
if (VideoHelper.canEnterPipMode(this)) {
|
||||||
|
Log.v(TAG, "enabling pip")
|
||||||
|
enterPipMode()
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "unable to use pip")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Deal with bad entries from older version
|
||||||
|
Log.v(TAG, "No setting, fallback")
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
override fun onBackPressed() {
|
||||||
|
Log.v(TAG, "onBackPressed()...")
|
||||||
|
val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE)
|
||||||
|
val videoPlayerFragment =
|
||||||
|
(supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!!
|
||||||
|
|
||||||
|
// copying Youtube behavior to have back button exit full screen.
|
||||||
|
if (videoPlayerFragment.getIsFullscreen()) {
|
||||||
|
Log.v(TAG, "exiting full screen")
|
||||||
|
videoPlayerFragment.fullScreenToggle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// pause video if pref is enabled
|
||||||
|
if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) {
|
||||||
|
videoPlayerFragment.pauseVideo()
|
||||||
|
}
|
||||||
|
val backgroundBehavior = sharedPref.getString(
|
||||||
|
getString(R.string.pref_background_behavior_key),
|
||||||
|
getString(R.string.pref_background_stop_key)
|
||||||
|
)!!
|
||||||
|
if (backgroundBehavior == getString(R.string.pref_background_stop_key)) {
|
||||||
|
Log.v(TAG, "stop the video")
|
||||||
|
videoPlayerFragment.pauseVideo()
|
||||||
|
stopService(Intent(this, VideoPlayerService::class.java))
|
||||||
|
super.onBackPressed()
|
||||||
|
} else if (backgroundBehavior == getString(R.string.pref_background_audio_key)) {
|
||||||
|
Log.v(TAG, "play the Audio")
|
||||||
|
super.onBackPressed()
|
||||||
|
} else if (backgroundBehavior == getString(R.string.pref_background_float_key)) {
|
||||||
|
Log.v(TAG, "play in floating video")
|
||||||
|
//canEnterPIPMode makes sure API level is high enough
|
||||||
|
if (VideoHelper.canEnterPipMode(this)) {
|
||||||
|
Log.v(TAG, "enabling pip")
|
||||||
|
enterPipMode()
|
||||||
|
//fixes problem where back press doesn't bring up video list after returning from PIP mode
|
||||||
|
val intentSettings = Intent(this, VideoListActivity::class.java)
|
||||||
|
this.startActivity(intentSettings)
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Unable to enter PIP mode")
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Deal with bad entries from older version
|
||||||
|
Log.v(TAG, "No setting, fallback")
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
fun enterPipMode() {
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||||
|
if (videoPlayerFragment!!.videoAspectRatio == 0.toFloat()) {
|
||||||
|
Log.i(TAG, "impossible to switch to pip")
|
||||||
|
} else {
|
||||||
|
val rational = Rational((videoPlayerFragment.videoAspectRatio * 100).toInt(), 100)
|
||||||
|
val mParams = PictureInPictureParams.Builder()
|
||||||
|
.setAspectRatio(rational) // .setSourceRectHint(new Rect(0,500,400,600))
|
||||||
|
.build()
|
||||||
|
enterPictureInPictureMode(mParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPictureInPictureModeChanged(
|
||||||
|
isInPictureInPictureMode: Boolean,
|
||||||
|
newConfig: Configuration
|
||||||
|
) {
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
val videoPlayerFragment =
|
||||||
|
fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?
|
||||||
|
if (videoPlayerFragment != null) {
|
||||||
|
if (isInPictureInPictureMode) {
|
||||||
|
changedToPipMode()
|
||||||
|
Log.v(TAG, "switched to pip ")
|
||||||
|
videoPlayerFragment.useController(false)
|
||||||
|
} else {
|
||||||
|
changedToNormalMode()
|
||||||
|
Log.v(TAG, "switched to normal")
|
||||||
|
videoPlayerFragment.useController(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "videoPlayerFragment is NULL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "VideoPlayActivity"
|
||||||
|
var floatMode = false
|
||||||
|
private const val REQUEST_CODE = 101
|
||||||
|
}
|
||||||
|
}
|
@ -4,19 +4,14 @@ import android.view.LayoutInflater
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import net.schueller.peertube.R
|
import net.schueller.peertube.R
|
||||||
import net.schueller.peertube.databinding.ItemCategoryTitleBinding
|
import net.schueller.peertube.databinding.*
|
||||||
import net.schueller.peertube.databinding.ItemChannelTitleBinding
|
import net.schueller.peertube.fragment.VideoMetaDataFragment
|
||||||
import net.schueller.peertube.databinding.ItemTagTitleBinding
|
import net.schueller.peertube.model.*
|
||||||
import net.schueller.peertube.databinding.RowVideoListBinding
|
|
||||||
import net.schueller.peertube.model.Category
|
|
||||||
import net.schueller.peertube.model.Channel
|
|
||||||
import net.schueller.peertube.model.TagVideo
|
|
||||||
import net.schueller.peertube.model.Video
|
|
||||||
import net.schueller.peertube.model.VideoList
|
|
||||||
import net.schueller.peertube.model.ui.OverviewRecycleViewItem
|
import net.schueller.peertube.model.ui.OverviewRecycleViewItem
|
||||||
|
import net.schueller.peertube.model.ui.VideoMetaViewItem
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHolder>() {
|
class MultiViewRecycleViewAdapter(private val videoMetaDataFragment: VideoMetaDataFragment? = null) : RecyclerView.Adapter<MultiViewRecyclerViewHolder>() {
|
||||||
|
|
||||||
private var items = ArrayList<OverviewRecycleViewItem>()
|
private var items = ArrayList<OverviewRecycleViewItem>()
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -34,6 +29,11 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setVideoMeta(videoMetaViewItem: VideoMetaViewItem) {
|
||||||
|
items.add(videoMetaViewItem)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
fun setCategoryTitle(category: Category) {
|
fun setCategoryTitle(category: Category) {
|
||||||
items.add(category)
|
items.add(category)
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
@ -49,6 +49,11 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setVideoComment(commentThread: CommentThread) {
|
||||||
|
items.add(commentThread)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
fun clearData() {
|
fun clearData() {
|
||||||
items.clear()
|
items.clear()
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
@ -83,6 +88,21 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
R.layout.item_video_meta -> MultiViewRecyclerViewHolder.VideoMetaViewHolder(
|
||||||
|
ItemVideoMetaBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
videoMetaDataFragment
|
||||||
|
)
|
||||||
|
R.layout.item_video_comments_overview -> MultiViewRecyclerViewHolder.VideoCommentsViewHolder(
|
||||||
|
ItemVideoCommentsOverviewBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
else -> throw IllegalArgumentException("Invalid ViewType Provided")
|
else -> throw IllegalArgumentException("Invalid ViewType Provided")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +113,8 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||||||
is MultiViewRecyclerViewHolder.CategoryViewHolder -> holder.bind(items[position] as Category)
|
is MultiViewRecyclerViewHolder.CategoryViewHolder -> holder.bind(items[position] as Category)
|
||||||
is MultiViewRecyclerViewHolder.ChannelViewHolder -> holder.bind(items[position] as Channel)
|
is MultiViewRecyclerViewHolder.ChannelViewHolder -> holder.bind(items[position] as Channel)
|
||||||
is MultiViewRecyclerViewHolder.TagViewHolder -> holder.bind(items[position] as TagVideo)
|
is MultiViewRecyclerViewHolder.TagViewHolder -> holder.bind(items[position] as TagVideo)
|
||||||
|
is MultiViewRecyclerViewHolder.VideoMetaViewHolder -> holder.bind(items[position] as VideoMetaViewItem)
|
||||||
|
is MultiViewRecyclerViewHolder.VideoCommentsViewHolder -> holder.bind(items[position] as CommentThread)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +126,8 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter<MultiViewRecyclerViewHo
|
|||||||
is Channel -> R.layout.item_channel_title
|
is Channel -> R.layout.item_channel_title
|
||||||
is Category -> R.layout.item_category_title
|
is Category -> R.layout.item_category_title
|
||||||
is TagVideo -> R.layout.item_tag_title
|
is TagVideo -> R.layout.item_tag_title
|
||||||
|
is VideoMetaViewItem -> R.layout.item_video_meta
|
||||||
|
is CommentThread -> R.layout.item_video_comments_overview
|
||||||
else -> { return 0}
|
else -> { return 0}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,47 +16,286 @@
|
|||||||
*/
|
*/
|
||||||
package net.schueller.peertube.adapter
|
package net.schueller.peertube.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import net.schueller.peertube.R
|
|
||||||
import net.schueller.peertube.R.color
|
import net.schueller.peertube.R.color
|
||||||
import net.schueller.peertube.R.string
|
import net.schueller.peertube.R.string
|
||||||
import net.schueller.peertube.activity.AccountActivity
|
import net.schueller.peertube.activity.AccountActivity
|
||||||
import net.schueller.peertube.activity.VideoListActivity
|
import net.schueller.peertube.activity.VideoListActivity
|
||||||
import net.schueller.peertube.activity.VideoListActivity.Companion
|
|
||||||
import net.schueller.peertube.activity.VideoPlayActivity
|
import net.schueller.peertube.activity.VideoPlayActivity
|
||||||
import net.schueller.peertube.databinding.ItemCategoryTitleBinding
|
|
||||||
import net.schueller.peertube.databinding.ItemChannelTitleBinding
|
|
||||||
import net.schueller.peertube.databinding.RowVideoListBinding
|
|
||||||
import net.schueller.peertube.helper.APIUrlHelper
|
import net.schueller.peertube.helper.APIUrlHelper
|
||||||
import net.schueller.peertube.helper.MetaDataHelper.getDuration
|
import net.schueller.peertube.helper.MetaDataHelper.getDuration
|
||||||
import net.schueller.peertube.helper.MetaDataHelper.getMetaString
|
import net.schueller.peertube.helper.MetaDataHelper.getMetaString
|
||||||
import net.schueller.peertube.helper.MetaDataHelper.getOwnerString
|
import net.schueller.peertube.helper.MetaDataHelper.getOwnerString
|
||||||
import net.schueller.peertube.model.Avatar
|
|
||||||
import net.schueller.peertube.model.Category
|
|
||||||
import net.schueller.peertube.model.Channel
|
|
||||||
import net.schueller.peertube.model.Video
|
|
||||||
import com.mikepenz.iconics.Iconics.Builder
|
import com.mikepenz.iconics.Iconics.Builder
|
||||||
import net.schueller.peertube.R.id
|
import net.schueller.peertube.R.id
|
||||||
import net.schueller.peertube.R.menu
|
import net.schueller.peertube.R.menu
|
||||||
import net.schueller.peertube.databinding.ItemTagTitleBinding
|
import net.schueller.peertube.databinding.*
|
||||||
|
import net.schueller.peertube.fragment.VideoMetaDataFragment
|
||||||
import net.schueller.peertube.intents.Intents
|
import net.schueller.peertube.intents.Intents
|
||||||
import net.schueller.peertube.model.TagVideo
|
import net.schueller.peertube.model.*
|
||||||
|
import net.schueller.peertube.model.ui.VideoMetaViewItem
|
||||||
|
import net.schueller.peertube.network.GetVideoDataService
|
||||||
|
import net.schueller.peertube.network.RetrofitInstance
|
||||||
|
import net.schueller.peertube.network.Session
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import net.schueller.peertube.R
|
||||||
|
import net.schueller.peertube.network.GetUserService
|
||||||
|
|
||||||
|
|
||||||
sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
|
sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
var videoRating: Rating? = null
|
||||||
|
var isLeaveAppExpected = false
|
||||||
|
|
||||||
class CategoryViewHolder(private val binding: ItemCategoryTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
class CategoryViewHolder(private val binding: ItemCategoryTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
||||||
fun bind(category: Category) {
|
fun bind(category: Category) {
|
||||||
binding.textViewTitle.text = category.label
|
binding.textViewTitle.text = category.label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class VideoCommentsViewHolder(private val binding: ItemVideoCommentsOverviewBinding): MultiViewRecyclerViewHolder(binding) {
|
||||||
|
fun bind(commentThread: CommentThread) {
|
||||||
|
|
||||||
|
binding.videoCommentsTotalCount.text = commentThread.total.toString()
|
||||||
|
|
||||||
|
if (commentThread.comments.isNotEmpty()) {
|
||||||
|
val highlightedComment: Comment = commentThread.comments[0]
|
||||||
|
|
||||||
|
// owner / creator Avatar
|
||||||
|
val avatar = highlightedComment.account.avatar
|
||||||
|
if (avatar != null) {
|
||||||
|
val baseUrl = APIUrlHelper.getUrl(binding.videoHighlightedAvatar.context)
|
||||||
|
val avatarPath = avatar.path
|
||||||
|
Picasso.get()
|
||||||
|
.load(baseUrl + avatarPath)
|
||||||
|
.into(binding.videoHighlightedAvatar)
|
||||||
|
}
|
||||||
|
binding.videoHighlightedComment.text = highlightedComment.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VideoMetaViewHolder(private val binding: ItemVideoMetaBinding, private val videoMetaDataFragment: VideoMetaDataFragment?): MultiViewRecyclerViewHolder(binding) {
|
||||||
|
fun bind(videoMetaViewItem: VideoMetaViewItem) {
|
||||||
|
|
||||||
|
val video = videoMetaViewItem.video
|
||||||
|
|
||||||
|
if (video != null && videoMetaDataFragment != null) {
|
||||||
|
|
||||||
|
val context = binding.avatar.context
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||||
|
apiBaseURL,
|
||||||
|
APIUrlHelper.useInsecureConnection(context)
|
||||||
|
).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
val userService = RetrofitInstance.getRetrofitInstance(
|
||||||
|
apiBaseURL,
|
||||||
|
APIUrlHelper.useInsecureConnection(context)
|
||||||
|
).create(
|
||||||
|
GetUserService::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
// Title
|
||||||
|
binding.videoName.text = video.name
|
||||||
|
binding.videoOpenDescription.setOnClickListener {
|
||||||
|
videoMetaDataFragment.showDescriptionFragment(video)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbs up
|
||||||
|
binding.videoThumbsUpWrapper.setOnClickListener {
|
||||||
|
rateVideo(true, video, context, binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbs Down
|
||||||
|
binding.videoThumbsDownWrapper.setOnClickListener {
|
||||||
|
rateVideo(false, video, context, binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.videoAddToPlaylistWrapper.setOnClickListener {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(string.video_feature_not_yet_implemented),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.videoBlockWrapper.setOnClickListener {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(string.video_feature_not_yet_implemented),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.videoFlagWrapper.setOnClickListener {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(string.video_feature_not_yet_implemented),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// video rating
|
||||||
|
videoRating = Rating()
|
||||||
|
videoRating!!.rating = RATING_NONE // default
|
||||||
|
updateVideoRating(video, binding)
|
||||||
|
|
||||||
|
// Retrieve which rating the user gave to this video
|
||||||
|
if (Session.getInstance().isLoggedIn) {
|
||||||
|
val call = videoDataService.getVideoRating(video.id)
|
||||||
|
call.enqueue(object : Callback<Rating?> {
|
||||||
|
override fun onResponse(call: Call<Rating?>, response: Response<Rating?>) {
|
||||||
|
videoRating = response.body()
|
||||||
|
updateVideoRating(video, binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Rating?>, t: Throwable) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Share
|
||||||
|
binding.videoShare.setOnClickListener {
|
||||||
|
isLeaveAppExpected = true
|
||||||
|
Intents.Share(context, video)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide download if not supported by video
|
||||||
|
if (video.downloadEnabled) {
|
||||||
|
binding.videoDownloadWrapper.setOnClickListener {
|
||||||
|
Intents.Download(context, video)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.videoDownloadWrapper.visibility = GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
val account = video.account
|
||||||
|
|
||||||
|
// owner / creator Avatar
|
||||||
|
val avatar = account.avatar
|
||||||
|
if (avatar != null) {
|
||||||
|
val baseUrl = APIUrlHelper.getUrl(context)
|
||||||
|
val avatarPath = avatar.path
|
||||||
|
Picasso.get()
|
||||||
|
.load(baseUrl + avatarPath)
|
||||||
|
.into(binding.avatar)
|
||||||
|
}
|
||||||
|
// created at / views
|
||||||
|
binding.videoMeta.text = getMetaString(
|
||||||
|
video.createdAt,
|
||||||
|
video.views,
|
||||||
|
context!!
|
||||||
|
)
|
||||||
|
|
||||||
|
// owner / creator
|
||||||
|
binding.videoOwner.text = getOwnerString(
|
||||||
|
video.account.name,
|
||||||
|
video.account.host,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
// videoOwnerSubscribers
|
||||||
|
binding.videoOwnerSubscribers.text = video.account.followersCount.toString()
|
||||||
|
|
||||||
|
|
||||||
|
// get subscription status
|
||||||
|
var isSubscribed = false
|
||||||
|
|
||||||
|
if (Session.getInstance().isLoggedIn) {
|
||||||
|
val subChannel = video.channel.name + "@" + video.channel.host
|
||||||
|
val call = userService.subscriptionsExist(subChannel)
|
||||||
|
call.enqueue(object : Callback<JsonObject> {
|
||||||
|
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
// {"video.channel.name + "@" + video.channel.host":true}
|
||||||
|
if (response.body()?.get(video.channel.name + "@" + video.channel.host)!!.asBoolean) {
|
||||||
|
binding.videoOwnerSubscribeButton.setText(string.unsubscribe)
|
||||||
|
isSubscribed = true;
|
||||||
|
} else {
|
||||||
|
binding.videoOwnerSubscribeButton.setText(string.subscribe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onFailure(call: Call<JsonObject>, t: Throwable) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.videoOwnerSubscribeButton.setOnClickListener {
|
||||||
|
if (Session.getInstance().isLoggedIn) {
|
||||||
|
if (!isSubscribed) {
|
||||||
|
val payload = video.channel.name + "@" + video.channel.host
|
||||||
|
val body = "{\"uri\":\"$payload\"}".toRequestBody("application/json".toMediaType())
|
||||||
|
val call = userService.subscribe(body)
|
||||||
|
call.enqueue(object : Callback<ResponseBody?> {
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<ResponseBody?>,
|
||||||
|
response: Response<ResponseBody?>
|
||||||
|
) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
binding.videoOwnerSubscribeButton.setText(string.unsubscribe)
|
||||||
|
isSubscribed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
val payload = video.channel.name + "@" + video.channel.host
|
||||||
|
val call = userService.unsubscribe(payload)
|
||||||
|
call.enqueue(object : Callback<ResponseBody?> {
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<ResponseBody?>,
|
||||||
|
response: Response<ResponseBody?>
|
||||||
|
) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
binding.videoOwnerSubscribeButton.setText(string.subscribe)
|
||||||
|
isSubscribed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(string.video_login_required_for_service),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ChannelViewHolder(private val binding: ItemChannelTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
class ChannelViewHolder(private val binding: ItemChannelTitleBinding): MultiViewRecyclerViewHolder(binding) {
|
||||||
fun bind(channel: Channel) {
|
fun bind(channel: Channel) {
|
||||||
|
|
||||||
@ -178,4 +417,117 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun updateVideoRating(video: Video?, binding: ItemVideoMetaBinding) {
|
||||||
|
|
||||||
|
when (videoRating!!.rating) {
|
||||||
|
RATING_NONE -> {
|
||||||
|
Log.v("MWCVH", "RATING_NONE")
|
||||||
|
binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up)
|
||||||
|
binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down)
|
||||||
|
}
|
||||||
|
RATING_LIKE -> {
|
||||||
|
Log.v("MWCVH", "RATING_LIKE")
|
||||||
|
binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up_filled)
|
||||||
|
binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down)
|
||||||
|
}
|
||||||
|
RATING_DISLIKE -> {
|
||||||
|
Log.v("MWCVH", "RATING_DISLIKE")
|
||||||
|
binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up)
|
||||||
|
binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down_filled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the texts
|
||||||
|
binding.videoThumbsUpTotal.text = video?.likes.toString()
|
||||||
|
binding.videoThumbsDownTotal.text = video?.dislikes.toString()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: move this out and get update when rating changes
|
||||||
|
*/
|
||||||
|
fun rateVideo(like: Boolean, video: Video, context: Context, binding: ItemVideoMetaBinding) {
|
||||||
|
if (Session.getInstance().isLoggedIn) {
|
||||||
|
val ratePayload: String = when (videoRating!!.rating) {
|
||||||
|
RATING_LIKE -> if (like) RATING_NONE else RATING_DISLIKE
|
||||||
|
RATING_DISLIKE -> if (like) RATING_LIKE else RATING_NONE
|
||||||
|
RATING_NONE -> if (like) RATING_LIKE else RATING_DISLIKE
|
||||||
|
else -> if (like) RATING_LIKE else RATING_DISLIKE
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = "{\"rating\":\"$ratePayload\"}".toRequestBody("application/json".toMediaType())
|
||||||
|
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||||
|
apiBaseURL, APIUrlHelper.useInsecureConnection(
|
||||||
|
context
|
||||||
|
)
|
||||||
|
).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
val call = videoDataService.rateVideo(video.id, body)
|
||||||
|
call.enqueue(object : Callback<ResponseBody?> {
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<ResponseBody?>,
|
||||||
|
response: Response<ResponseBody?>
|
||||||
|
) {
|
||||||
|
// if 20x, update likes/dislikes
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val previousRating = videoRating!!.rating
|
||||||
|
|
||||||
|
// Update the likes/dislikes count of the video, if needed.
|
||||||
|
// This is only a visual trick, as the actual like/dislike count has
|
||||||
|
// already been modified on the PeerTube instance.
|
||||||
|
if (previousRating != ratePayload) {
|
||||||
|
when (previousRating) {
|
||||||
|
RATING_NONE -> if (ratePayload == RATING_LIKE) {
|
||||||
|
video.likes = video.likes + 1
|
||||||
|
} else {
|
||||||
|
video.dislikes = video.dislikes + 1
|
||||||
|
}
|
||||||
|
RATING_LIKE -> {
|
||||||
|
video.likes = video.likes - 1
|
||||||
|
if (ratePayload == RATING_DISLIKE) {
|
||||||
|
video.dislikes = video.dislikes + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RATING_DISLIKE -> {
|
||||||
|
video.dislikes = video.dislikes - 1
|
||||||
|
if (ratePayload == RATING_LIKE) {
|
||||||
|
video.likes = video.likes + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
videoRating!!.rating = ratePayload
|
||||||
|
updateVideoRating(video, binding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(string.video_rating_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(string.video_login_required_for_service),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val RATING_NONE = "none"
|
||||||
|
private const val RATING_LIKE = "like"
|
||||||
|
private const val RATING_DISLIKE = "dislike"
|
||||||
|
const val EXTRA_VIDEOID = "VIDEOID"
|
||||||
|
const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import android.os.Parcelable
|
|||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Entity(tableName = "server_table")
|
@Entity(tableName = "server_table")
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package net.schueller.peertube.database
|
package net.schueller.peertube.database
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.AsyncTask
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -19,12 +19,13 @@ package net.schueller.peertube.fragment
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import net.schueller.peertube.R
|
import net.schueller.peertube.R
|
||||||
@ -52,7 +53,7 @@ class AddServerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
mBinding = FragmentAddServerBinding.inflate(inflater, container, false)
|
mBinding = FragmentAddServerBinding.inflate(inflater, container, false)
|
||||||
return mBinding.root
|
return mBinding.root
|
||||||
}
|
}
|
||||||
@ -115,7 +116,7 @@ class AddServerFragment : Fragment() {
|
|||||||
|
|
||||||
mBinding.pickServerUrl.setOnClickListener {
|
mBinding.pickServerUrl.setOnClickListener {
|
||||||
val intentServer = Intent(activity, SearchServerActivity::class.java)
|
val intentServer = Intent(activity, SearchServerActivity::class.java)
|
||||||
this.startActivityForResult(intentServer, PICK_SERVER)
|
openActivityForResult(intentServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,35 +133,24 @@ class AddServerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
val intent = result.data
|
||||||
|
val serverUrlTest = intent?.getStringExtra("serverUrl")
|
||||||
if (requestCode != PICK_SERVER) {
|
mBinding.serverUrl.setText(serverUrlTest)
|
||||||
return
|
mBinding.serverLabel.apply {
|
||||||
}
|
if (text.toString().isBlank()) {
|
||||||
|
setText(intent?.getStringExtra("serverName"))
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val serverUrlTest = data?.getStringExtra("serverUrl")
|
|
||||||
//Log.d(TAG, "serverUrl " + serverUrlTest);
|
|
||||||
|
|
||||||
mBinding.serverUrl.setText(serverUrlTest)
|
|
||||||
|
|
||||||
mBinding.serverLabel.apply {
|
|
||||||
if (text.toString().isBlank()) {
|
|
||||||
setText(data?.getStringExtra("serverName"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openActivityForResult(intent: Intent) {
|
||||||
|
resultLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "AddServerFragment"
|
|
||||||
private const val PICK_SERVER = 1
|
|
||||||
|
|
||||||
private const val SERVER_ARG = "server"
|
private const val SERVER_ARG = "server"
|
||||||
|
|
||||||
fun newInstance(server: Server) = AddServerFragment().apply {
|
fun newInstance(server: Server) = AddServerFragment().apply {
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.fragment
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import net.schueller.peertube.R
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import net.schueller.peertube.helper.APIUrlHelper
|
||||||
|
import net.schueller.peertube.helper.ErrorHelper
|
||||||
|
import net.schueller.peertube.model.Description
|
||||||
|
import net.schueller.peertube.model.Video
|
||||||
|
import net.schueller.peertube.network.GetVideoDataService
|
||||||
|
import net.schueller.peertube.network.RetrofitInstance
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
|
class VideoDescriptionFragment : Fragment () {
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = inflater.inflate(
|
||||||
|
R.layout.fragment_video_description, container,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
val video = video
|
||||||
|
|
||||||
|
if (video != null) {
|
||||||
|
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||||
|
apiBaseURL,
|
||||||
|
APIUrlHelper.useInsecureConnection(context)
|
||||||
|
).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
// description, get extended if available
|
||||||
|
val videoDescription = view.findViewById<TextView>(R.id.description)
|
||||||
|
val shortDescription = video.description
|
||||||
|
if (shortDescription != null && shortDescription.length > 237) {
|
||||||
|
val call = videoDataService.getVideoFullDescription(video.uuid);
|
||||||
|
call.enqueue(object : Callback<Description?> {
|
||||||
|
override fun onResponse(call: Call<Description?>, response: Response<Description?>) {
|
||||||
|
val videoFullDescription: Description? = response.body();
|
||||||
|
|
||||||
|
videoDescription.text = videoFullDescription?.description
|
||||||
|
}
|
||||||
|
override fun onFailure(call: Call<Description?>, t: Throwable) {
|
||||||
|
Log.wtf(TAG, t.fillInStackTrace())
|
||||||
|
ErrorHelper.showToastFromCommunicationError(activity, t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
videoDescription.text = shortDescription;
|
||||||
|
|
||||||
|
val closeButton = view.findViewById<ImageButton>(R.id.video_description_close_button)
|
||||||
|
closeButton.setOnClickListener {
|
||||||
|
videoMetaDataFragment!!.hideDescriptionFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
// video privacy
|
||||||
|
val videoPrivacy = view.findViewById<TextView>(R.id.video_privacy);
|
||||||
|
videoPrivacy.text = video!!.privacy.label;
|
||||||
|
|
||||||
|
// video category
|
||||||
|
val videoCategory = view.findViewById<TextView>(R.id.video_category);
|
||||||
|
videoCategory.text = video!!.category.label;
|
||||||
|
|
||||||
|
// video privacy
|
||||||
|
val videoLicense = view.findViewById<TextView>(R.id.video_license);
|
||||||
|
videoLicense.text = video!!.licence.label;
|
||||||
|
|
||||||
|
// video language
|
||||||
|
val videoLanguage = view.findViewById<TextView>(R.id.video_language);
|
||||||
|
videoLanguage.text = video!!.language.label;
|
||||||
|
|
||||||
|
// video privacy
|
||||||
|
val videoTags = view.findViewById<TextView>(R.id.video_tags);
|
||||||
|
videoTags.text = android.text.TextUtils.join(", ", video!!.tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var video: Video? = null
|
||||||
|
private var videoMetaDataFragment: VideoMetaDataFragment? = null
|
||||||
|
const val TAG = "VideoDescr"
|
||||||
|
fun newInstance(mVideo: Video?, mVideoMetaDataFragment: VideoMetaDataFragment): VideoDescriptionFragment {
|
||||||
|
video = mVideo
|
||||||
|
videoMetaDataFragment = mVideoMetaDataFragment
|
||||||
|
return VideoDescriptionFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,408 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package net.schueller.peertube.fragment;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
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.iconics.Iconics;
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import net.schueller.peertube.R;
|
|
||||||
import net.schueller.peertube.helper.APIUrlHelper;
|
|
||||||
import net.schueller.peertube.helper.ErrorHelper;
|
|
||||||
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.Description;
|
|
||||||
import net.schueller.peertube.model.Rating;
|
|
||||||
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 androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
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";
|
|
||||||
|
|
||||||
private static final String RATING_NONE = "none";
|
|
||||||
private static final String RATING_LIKE = "like";
|
|
||||||
private static final String RATING_DISLIKE = "dislike";
|
|
||||||
|
|
||||||
private Rating videoRating;
|
|
||||||
private ColorStateList defaultTextColor;
|
|
||||||
|
|
||||||
private boolean leaveAppExpected = false;
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause()
|
|
||||||
{
|
|
||||||
leaveAppExpected = false;
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLeaveAppExpected()
|
|
||||||
{
|
|
||||||
return leaveAppExpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateVideoMeta(Video video, VideoPlayerService mService) {
|
|
||||||
Context context = getContext();
|
|
||||||
Activity activity = getActivity();
|
|
||||||
|
|
||||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
|
||||||
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class);
|
|
||||||
|
|
||||||
// Thumbs up
|
|
||||||
Button thumbsUpButton = activity.findViewById(R.id.video_thumbs_up);
|
|
||||||
defaultTextColor = thumbsUpButton.getTextColors();
|
|
||||||
thumbsUpButton.setText(R.string.video_thumbs_up_icon);
|
|
||||||
new Iconics.Builder().on(thumbsUpButton).build();
|
|
||||||
thumbsUpButton.setOnClickListener(v -> {
|
|
||||||
rateVideo(true, video);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Thumbs Down
|
|
||||||
Button thumbsDownButton = activity.findViewById(R.id.video_thumbs_down);
|
|
||||||
thumbsDownButton.setText(R.string.video_thumbs_down_icon);
|
|
||||||
new Iconics.Builder().on(thumbsDownButton).build();
|
|
||||||
thumbsDownButton.setOnClickListener(v -> {
|
|
||||||
rateVideo(false, video);
|
|
||||||
});
|
|
||||||
|
|
||||||
// video rating
|
|
||||||
videoRating = new Rating();
|
|
||||||
videoRating.setRating(RATING_NONE); // default
|
|
||||||
updateVideoRating(video);
|
|
||||||
|
|
||||||
// Retrieve which rating the user gave to this video
|
|
||||||
if (Session.getInstance().isLoggedIn()) {
|
|
||||||
Call<Rating> call = videoDataService.getVideoRating(video.getId());
|
|
||||||
call.enqueue(new Callback<Rating>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(Call<Rating> call, Response<Rating> response) {
|
|
||||||
videoRating = response.body();
|
|
||||||
updateVideoRating(video);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<Rating> call, Throwable t) {
|
|
||||||
ErrorHelper.showToastFromCommunicationError( getActivity(), t );
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Share
|
|
||||||
Button videoShareButton = activity.findViewById(R.id.video_share);
|
|
||||||
videoShareButton.setText(R.string.video_share_icon);
|
|
||||||
new Iconics.Builder().on(videoShareButton).build();
|
|
||||||
videoShareButton.setOnClickListener(v ->
|
|
||||||
{
|
|
||||||
leaveAppExpected = true;
|
|
||||||
Intents.Share( context, video );
|
|
||||||
} );
|
|
||||||
|
|
||||||
// Download
|
|
||||||
Button videoDownloadButton = activity.findViewById(R.id.video_download);
|
|
||||||
videoDownloadButton.setText(R.string.video_download_icon);
|
|
||||||
new Iconics.Builder().on(videoDownloadButton).build();
|
|
||||||
videoDownloadButton.setOnClickListener(v -> {
|
|
||||||
// get permission to store file
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
leaveAppExpected = true;
|
|
||||||
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Intents.Download(context, video);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, getString(R.string.video_download_permission_error), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Intents.Download(context, video);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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.get()
|
|
||||||
.load(baseUrl + avatarPath)
|
|
||||||
.into(avatarView);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// title / name
|
|
||||||
TextView videoName = activity.findViewById(R.id.sl_row_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);
|
|
||||||
String shortDescription = video.getDescription();
|
|
||||||
if (shortDescription != null && Objects.requireNonNull(shortDescription).length() > 237) {
|
|
||||||
shortDescription += "\n" + getString(R.string.video_description_read_more);
|
|
||||||
videoDescription.setOnClickListener(v -> {
|
|
||||||
Call<Description> call = videoDataService.getVideoFullDescription(video.getUuid());
|
|
||||||
call.enqueue(new Callback<Description>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(Call<Description> call, Response<Description> response) {
|
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
|
||||||
new Description();
|
|
||||||
Description videoFullDescription;
|
|
||||||
videoFullDescription = response.body();
|
|
||||||
videoDescription.setText(videoFullDescription.getDescription());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<Description> call, Throwable t) {
|
|
||||||
Toast.makeText(getContext(), getString(R.string.video_get_full_description_failed), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
videoDescription.setText(shortDescription);
|
|
||||||
|
|
||||||
// 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 language
|
|
||||||
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.Builder().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.Builder().on(videoOptions).build();
|
|
||||||
|
|
||||||
videoOptions.setOnClickListener(v -> {
|
|
||||||
VideoOptionsFragment videoOptionsFragment =
|
|
||||||
VideoOptionsFragment.newInstance(mService, video.getFiles());
|
|
||||||
videoOptionsFragment.show(getActivity().getSupportFragmentManager(),
|
|
||||||
VideoOptionsFragment.TAG);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateVideoRating(Video video) {
|
|
||||||
Button thumbsUpButton = getActivity().findViewById(R.id.video_thumbs_up);
|
|
||||||
Button thumbsDownButton = getActivity().findViewById(R.id.video_thumbs_down);
|
|
||||||
|
|
||||||
TypedValue typedValue = new TypedValue();
|
|
||||||
|
|
||||||
TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimary});
|
|
||||||
int accentColor = a.getColor(0, 0);
|
|
||||||
|
|
||||||
// Change the color of the thumbs
|
|
||||||
switch (videoRating.getRating()) {
|
|
||||||
case RATING_NONE:
|
|
||||||
thumbsUpButton.setTextColor(defaultTextColor);
|
|
||||||
thumbsDownButton.setTextColor(defaultTextColor);
|
|
||||||
break;
|
|
||||||
case RATING_LIKE:
|
|
||||||
thumbsUpButton.setTextColor(accentColor);
|
|
||||||
thumbsDownButton.setTextColor(defaultTextColor);
|
|
||||||
break;
|
|
||||||
case RATING_DISLIKE:
|
|
||||||
thumbsUpButton.setTextColor(defaultTextColor);
|
|
||||||
thumbsDownButton.setTextColor(accentColor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the texts
|
|
||||||
TextView thumbsDownTotal = getActivity().findViewById(R.id.video_thumbs_down_total);
|
|
||||||
TextView thumbsUpTotal = getActivity().findViewById(R.id.video_thumbs_up_total);
|
|
||||||
thumbsUpTotal.setText(String.valueOf(video.getLikes()));
|
|
||||||
thumbsDownTotal.setText(String.valueOf(video.getDislikes()));
|
|
||||||
|
|
||||||
a.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
void rateVideo(Boolean like, Video video) {
|
|
||||||
if (Session.getInstance().isLoggedIn()) {
|
|
||||||
final String ratePayload;
|
|
||||||
|
|
||||||
switch (videoRating.getRating()) {
|
|
||||||
case RATING_LIKE:
|
|
||||||
ratePayload = like ? RATING_NONE : RATING_DISLIKE;
|
|
||||||
break;
|
|
||||||
case RATING_DISLIKE:
|
|
||||||
ratePayload = like ? RATING_LIKE : RATING_NONE;
|
|
||||||
break;
|
|
||||||
case RATING_NONE:
|
|
||||||
default:
|
|
||||||
ratePayload = like ? RATING_LIKE : RATING_DISLIKE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), "{\"rating\":\"" + ratePayload + "\"}");
|
|
||||||
|
|
||||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(getContext());
|
|
||||||
GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(getContext())).create(GetVideoDataService.class);
|
|
||||||
|
|
||||||
Call<ResponseBody> call = videoDataService.rateVideo(video.getId(), body);
|
|
||||||
|
|
||||||
call.enqueue(new Callback<ResponseBody>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
|
|
||||||
//Log.v(TAG, response.toString());
|
|
||||||
|
|
||||||
// if 20x, update likes/dislikes
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
String previousRating = videoRating.getRating();
|
|
||||||
|
|
||||||
// Update the likes/dislikes count of the video, if needed.
|
|
||||||
// This is only a visual trick, as the actual like/dislike count has
|
|
||||||
// already been modified on the PeerTube instance.
|
|
||||||
if (!previousRating.equals(ratePayload)) {
|
|
||||||
switch (previousRating) {
|
|
||||||
case RATING_NONE:
|
|
||||||
if (ratePayload.equals(RATING_LIKE)) {
|
|
||||||
video.setLikes(video.getLikes() + 1);
|
|
||||||
} else {
|
|
||||||
video.setDislikes(video.getDislikes() + 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RATING_LIKE:
|
|
||||||
video.setLikes(video.getLikes() - 1);
|
|
||||||
if (ratePayload.equals(RATING_DISLIKE)) {
|
|
||||||
video.setDislikes(video.getDislikes() + 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RATING_DISLIKE:
|
|
||||||
video.setDislikes(video.getDislikes() - 1);
|
|
||||||
if (ratePayload.equals(RATING_LIKE)) {
|
|
||||||
video.setLikes(video.getLikes() + 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
videoRating.setRating(ratePayload);
|
|
||||||
updateVideoRating(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
|
||||||
Toast.makeText(getContext(), getString(R.string.video_rating_failed), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), getString(R.string.video_login_required_for_service), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.fragment
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import net.schueller.peertube.helper.MetaDataHelper.getMetaString
|
||||||
|
import net.schueller.peertube.helper.MetaDataHelper.getOwnerString
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.os.Bundle
|
||||||
|
import net.schueller.peertube.R
|
||||||
|
import net.schueller.peertube.service.VideoPlayerService
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import net.schueller.peertube.helper.APIUrlHelper
|
||||||
|
import net.schueller.peertube.network.GetVideoDataService
|
||||||
|
import net.schueller.peertube.network.RetrofitInstance
|
||||||
|
import net.schueller.peertube.helper.ErrorHelper
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.mikepenz.iconics.Iconics
|
||||||
|
import net.schueller.peertube.adapter.MultiViewRecycleViewAdapter
|
||||||
|
import net.schueller.peertube.intents.Intents
|
||||||
|
import net.schueller.peertube.model.CommentThread
|
||||||
|
import net.schueller.peertube.model.Rating
|
||||||
|
import net.schueller.peertube.model.Video
|
||||||
|
import net.schueller.peertube.model.VideoList
|
||||||
|
import net.schueller.peertube.model.ui.VideoMetaViewItem
|
||||||
|
import net.schueller.peertube.network.GetUserService
|
||||||
|
import net.schueller.peertube.network.Session
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
class VideoMetaDataFragment : Fragment() {
|
||||||
|
private var videoRating: Rating? = null
|
||||||
|
private var defaultTextColor: ColorStateList? = null
|
||||||
|
private var recyclerView: RecyclerView? = null
|
||||||
|
private var mMultiViewAdapter: MultiViewRecycleViewAdapter? = null
|
||||||
|
|
||||||
|
private lateinit var videoDescriptionFragment: VideoDescriptionFragment
|
||||||
|
|
||||||
|
var isLeaveAppExpected = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
return inflater.inflate(R.layout.fragment_video_meta, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
isLeaveAppExpected = false
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showDescriptionFragment(video: Video) {
|
||||||
|
// show full description fragment
|
||||||
|
videoDescriptionFragment = VideoDescriptionFragment.newInstance(video, this)
|
||||||
|
childFragmentManager.beginTransaction()
|
||||||
|
.add(R.id.video_meta_data_fragment, videoDescriptionFragment, VideoDescriptionFragment.TAG).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideDescriptionFragment() {
|
||||||
|
val fragment: Fragment? = childFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG)
|
||||||
|
if (fragment != null) {
|
||||||
|
childFragmentManager.beginTransaction().remove(fragment).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateVideoMeta(video: Video, mService: VideoPlayerService?) {
|
||||||
|
|
||||||
|
// Remove description if it is open as we are loading a new video
|
||||||
|
hideDescriptionFragment()
|
||||||
|
|
||||||
|
val context = context
|
||||||
|
val activity: Activity? = activity
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val videoDataService = RetrofitInstance.getRetrofitInstance(
|
||||||
|
apiBaseURL,
|
||||||
|
APIUrlHelper.useInsecureConnection(context)
|
||||||
|
).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
// related videos
|
||||||
|
recyclerView = activity!!.findViewById(R.id.relatedVideosView)
|
||||||
|
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this@VideoMetaDataFragment.context)
|
||||||
|
recyclerView?.layoutManager = layoutManager
|
||||||
|
mMultiViewAdapter = MultiViewRecycleViewAdapter(this)
|
||||||
|
recyclerView?.adapter = mMultiViewAdapter
|
||||||
|
|
||||||
|
val videoMetaViewItem = VideoMetaViewItem()
|
||||||
|
videoMetaViewItem.video = video
|
||||||
|
mMultiViewAdapter?.setVideoMeta(videoMetaViewItem)
|
||||||
|
|
||||||
|
loadVideos()
|
||||||
|
|
||||||
|
// loadComments(video.id)
|
||||||
|
|
||||||
|
// mMultiViewAdapter?.setVideoComment()
|
||||||
|
|
||||||
|
// videoOwnerSubscribeButton
|
||||||
|
|
||||||
|
|
||||||
|
// description
|
||||||
|
|
||||||
|
|
||||||
|
// video player options
|
||||||
|
val videoOptions = activity.findViewById<TextView>(R.id.exo_more)
|
||||||
|
videoOptions.setText(R.string.video_more_icon)
|
||||||
|
Iconics.Builder().on(videoOptions).build()
|
||||||
|
videoOptions.setOnClickListener {
|
||||||
|
val videoOptionsFragment = VideoOptionsFragment.newInstance(mService, video.files)
|
||||||
|
videoOptionsFragment.show(
|
||||||
|
getActivity()!!.supportFragmentManager,
|
||||||
|
VideoOptionsFragment.TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadComments(videoId: Int) {
|
||||||
|
val context = context
|
||||||
|
|
||||||
|
val start = 0
|
||||||
|
val count = 1
|
||||||
|
val sort = "-createdAt"
|
||||||
|
|
||||||
|
|
||||||
|
// We set this to default to null so that on initial start there are videos listed.
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val service =
|
||||||
|
RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
val call: Call<CommentThread> = service.getCommentThreads(videoId, start, count, sort)
|
||||||
|
|
||||||
|
call.enqueue(object : Callback<CommentThread?> {
|
||||||
|
override fun onResponse(call: Call<CommentThread?>, response: Response<CommentThread?>) {
|
||||||
|
if (response.body() != null) {
|
||||||
|
val commentThread = response.body()
|
||||||
|
if (commentThread != null) {
|
||||||
|
mMultiViewAdapter!!.setVideoComment(commentThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<CommentThread?>, t: Throwable) {
|
||||||
|
Log.wtf("err", t.fillInStackTrace())
|
||||||
|
ErrorHelper.showToastFromCommunicationError(this@VideoMetaDataFragment.context, t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadVideos() {
|
||||||
|
val context = context
|
||||||
|
|
||||||
|
val start = 0
|
||||||
|
val count = 6
|
||||||
|
val sort = "-createdAt"
|
||||||
|
val filter: String? = null
|
||||||
|
|
||||||
|
val sharedPref = context?.getSharedPreferences(
|
||||||
|
context.packageName + "_preferences",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
var nsfw = "false"
|
||||||
|
var languages: Set<String>? = emptySet()
|
||||||
|
if (sharedPref != null) {
|
||||||
|
nsfw = if (sharedPref.getBoolean(getString(R.string.pref_show_nsfw_key), false)) "both" else "false"
|
||||||
|
languages = sharedPref.getStringSet(getString(R.string.pref_video_language_key), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set this to default to null so that on initial start there are videos listed.
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val service =
|
||||||
|
RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
val call: Call<VideoList> = service.getVideosData(start, count, sort, nsfw, filter, languages)
|
||||||
|
|
||||||
|
/*Log the URL called*/Log.d("URL Called", call.request().url.toString() + "")
|
||||||
|
// Toast.makeText(VideoListActivity.this, "URL Called: " + call.request().url(), Toast.LENGTH_SHORT).show();
|
||||||
|
call.enqueue(object : Callback<VideoList?> {
|
||||||
|
override fun onResponse(call: Call<VideoList?>, response: Response<VideoList?>) {
|
||||||
|
if (response.body() != null) {
|
||||||
|
val videoList = response.body()
|
||||||
|
if (videoList != null) {
|
||||||
|
mMultiViewAdapter!!.setVideoListData(videoList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<VideoList?>, t: Throwable) {
|
||||||
|
Log.wtf("err", t.fillInStackTrace())
|
||||||
|
ErrorHelper.showToastFromCommunicationError(this@VideoMetaDataFragment.context, t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
const val TAG = "VMDF"
|
||||||
|
}
|
||||||
|
}
|
@ -1,516 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package net.schueller.peertube.fragment;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.github.se_bastiaan.torrentstream.StreamStatus;
|
|
||||||
import com.github.se_bastiaan.torrentstream.Torrent;
|
|
||||||
import com.github.se_bastiaan.torrentstream.TorrentOptions;
|
|
||||||
import com.github.se_bastiaan.torrentstream.TorrentStream;
|
|
||||||
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerView;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
|
||||||
import com.mikepenz.iconics.Iconics;
|
|
||||||
|
|
||||||
import net.schueller.peertube.R;
|
|
||||||
|
|
||||||
import net.schueller.peertube.helper.APIUrlHelper;
|
|
||||||
import net.schueller.peertube.helper.ErrorHelper;
|
|
||||||
import net.schueller.peertube.model.File;
|
|
||||||
import net.schueller.peertube.model.Video;
|
|
||||||
import net.schueller.peertube.network.GetVideoDataService;
|
|
||||||
import net.schueller.peertube.network.RetrofitInstance;
|
|
||||||
import net.schueller.peertube.service.VideoPlayerService;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode;
|
|
||||||
|
|
||||||
public class VideoPlayerFragment extends Fragment implements VideoRendererEventListener {
|
|
||||||
|
|
||||||
private String mVideoUuid;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private PlayerView simpleExoPlayerView;
|
|
||||||
private Intent videoPlayerIntent;
|
|
||||||
private Boolean mBound = false;
|
|
||||||
private Boolean isFullscreen = false;
|
|
||||||
private VideoPlayerService mService;
|
|
||||||
private TorrentStream torrentStream;
|
|
||||||
private LinearLayout torrentStatus;
|
|
||||||
private float aspectRatio;
|
|
||||||
|
|
||||||
private static final String TAG = "VideoPlayerFragment";
|
|
||||||
private GestureDetector mDetector;
|
|
||||||
|
|
||||||
private ServiceConnection mConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
||||||
Log.d(TAG, "onServiceConnected");
|
|
||||||
VideoPlayerService.LocalBinder binder = (VideoPlayerService.LocalBinder) service;
|
|
||||||
mService = binder.getService();
|
|
||||||
|
|
||||||
// 2. Create the player
|
|
||||||
simpleExoPlayerView.setPlayer(mService.player);
|
|
||||||
mBound = true;
|
|
||||||
|
|
||||||
loadVideo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName componentName) {
|
|
||||||
Log.d(TAG, "onServiceDisconnected");
|
|
||||||
simpleExoPlayerView.setPlayer(null);
|
|
||||||
mBound = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private AspectRatioFrameLayout.AspectRatioListener aspectRatioListerner = new AspectRatioFrameLayout.AspectRatioListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onAspectRatioUpdated( float targetAspectRatio, float naturalAspectRatio, boolean aspectRatioMismatch )
|
|
||||||
{
|
|
||||||
aspectRatio = targetAspectRatio;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
return inflater.inflate(R.layout.fragment_video_player, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void start(String videoUuid) {
|
|
||||||
|
|
||||||
// start service
|
|
||||||
Context context = getContext();
|
|
||||||
Activity activity = getActivity();
|
|
||||||
|
|
||||||
mVideoUuid = videoUuid;
|
|
||||||
|
|
||||||
assert activity != null;
|
|
||||||
progressBar = activity.findViewById(R.id.torrent_progress);
|
|
||||||
progressBar.setMax(100);
|
|
||||||
|
|
||||||
assert context != null;
|
|
||||||
simpleExoPlayerView = new PlayerView(context);
|
|
||||||
simpleExoPlayerView = activity.findViewById(R.id.video_view);
|
|
||||||
|
|
||||||
simpleExoPlayerView.setControllerShowTimeoutMs(1000);
|
|
||||||
simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
|
||||||
|
|
||||||
mDetector = new GestureDetector(context, new MyGestureListener());
|
|
||||||
simpleExoPlayerView.setOnTouchListener(touchListener);
|
|
||||||
|
|
||||||
simpleExoPlayerView.setAspectRatioListener( aspectRatioListerner );
|
|
||||||
|
|
||||||
torrentStatus = activity.findViewById(R.id.exo_torrent_status);
|
|
||||||
|
|
||||||
// Full screen Icon
|
|
||||||
TextView fullscreenText = activity.findViewById(R.id.exo_fullscreen);
|
|
||||||
FrameLayout fullscreenButton = activity.findViewById(R.id.exo_fullscreen_button);
|
|
||||||
|
|
||||||
fullscreenText.setText(R.string.video_expand_icon);
|
|
||||||
new Iconics.Builder().on(fullscreenText).build();
|
|
||||||
|
|
||||||
fullscreenButton.setOnClickListener(view -> {
|
|
||||||
Log.d(TAG, "Fullscreen");
|
|
||||||
fullScreenToggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!mBound) {
|
|
||||||
videoPlayerIntent = new Intent(context, VideoPlayerService.class);
|
|
||||||
activity.bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadVideo() {
|
|
||||||
Context context = getContext();
|
|
||||||
|
|
||||||
|
|
||||||
// get video details from api
|
|
||||||
String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
|
||||||
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class);
|
|
||||||
|
|
||||||
Call<Video> call = service.getVideoData(mVideoUuid);
|
|
||||||
|
|
||||||
call.enqueue(new Callback<Video>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<Video> call, @NonNull Response<Video> response) {
|
|
||||||
|
|
||||||
Video video = response.body();
|
|
||||||
|
|
||||||
mService.setCurrentVideo(video);
|
|
||||||
|
|
||||||
if (video == null) {
|
|
||||||
Toast.makeText(context, "Unable to retrieve video information, try again later.", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
playVideo(video);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Video> call, @NonNull Throwable t) {
|
|
||||||
Log.wtf(TAG, t.fillInStackTrace());
|
|
||||||
ErrorHelper.showToastFromCommunicationError( getActivity(), t );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useController(boolean value) {
|
|
||||||
if (mBound) {
|
|
||||||
simpleExoPlayerView.setUseController(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void playVideo(Video video) {
|
|
||||||
|
|
||||||
Context context = getContext();
|
|
||||||
|
|
||||||
// video Meta fragment
|
|
||||||
VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment)
|
|
||||||
requireActivity().getSupportFragmentManager().findFragmentById(R.id.video_meta_data_fragment);
|
|
||||||
|
|
||||||
assert videoMetaDataFragment != null;
|
|
||||||
videoMetaDataFragment.updateVideoMeta(video, mService);
|
|
||||||
|
|
||||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
|
|
||||||
if (sharedPref.getBoolean(getString(R.string.pref_torrent_player_key), false)) {
|
|
||||||
torrentStatus.setVisibility(View.VISIBLE);
|
|
||||||
String stream = video.getFiles().get(0).getTorrentUrl();
|
|
||||||
Log.v(TAG, "getTorrentUrl : " + video.getFiles().get(0).getTorrentUrl());
|
|
||||||
torrentStream = setupTorrentStream();
|
|
||||||
torrentStream.startStream(stream);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Integer videoQuality = sharedPref.getInt(getString(R.string.pref_quality_key), 999999);
|
|
||||||
|
|
||||||
String urlToPlay = null;
|
|
||||||
boolean isHLS = false;
|
|
||||||
|
|
||||||
// try HLS stream first
|
|
||||||
// get video qualities
|
|
||||||
// TODO: if auto is set all versions except 0p should be added to a track and have exoplayer auto select optimal bitrate
|
|
||||||
if (video.getStreamingPlaylists().size() > 0) {
|
|
||||||
urlToPlay = video.getStreamingPlaylists().get( 0 ).getPlaylistUrl();
|
|
||||||
isHLS = true;
|
|
||||||
} else {
|
|
||||||
if (video.getFiles().size() > 0) {
|
|
||||||
urlToPlay = video.getFiles().get( 0 ).getFileUrl(); // default, take first found, usually highest res
|
|
||||||
for ( File file : video.getFiles() ) {
|
|
||||||
// Set quality if it matches
|
|
||||||
if ( file.getResolution().getId().equals( videoQuality ) ) {
|
|
||||||
urlToPlay = file.getFileUrl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!urlToPlay.isEmpty()) {
|
|
||||||
mService.setCurrentStreamUrl( urlToPlay, isHLS);
|
|
||||||
torrentStatus.setVisibility(View.GONE);
|
|
||||||
startPlayer();
|
|
||||||
} else {
|
|
||||||
stopVideo();
|
|
||||||
Toast.makeText(context, R.string.api_error, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.v(TAG, "end of load Video");
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPlayer() {
|
|
||||||
Util.startForegroundService(requireContext(), videoPlayerIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void destroyVideo() {
|
|
||||||
simpleExoPlayerView.setPlayer(null);
|
|
||||||
if (torrentStream != null) {
|
|
||||||
torrentStream.stopStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pauseVideo() {
|
|
||||||
if (mBound) {
|
|
||||||
mService.player.setPlayWhenReady(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pauseToggle() {
|
|
||||||
if (mBound) {
|
|
||||||
mService.player.setPlayWhenReady(!mService.player.getPlayWhenReady());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unPauseVideo() {
|
|
||||||
if (mBound) {
|
|
||||||
mService.player.setPlayWhenReady(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getVideoAspectRatio() { return aspectRatio; }
|
|
||||||
|
|
||||||
public boolean isPaused() {
|
|
||||||
return !mService.player.getPlayWhenReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showControls(boolean value) {
|
|
||||||
simpleExoPlayerView.setUseController(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopVideo() {
|
|
||||||
|
|
||||||
if (mBound) {
|
|
||||||
requireContext().unbindService(mConnection);
|
|
||||||
mBound = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsFullscreen(Boolean fullscreen) {
|
|
||||||
isFullscreen = fullscreen;
|
|
||||||
|
|
||||||
TextView fullscreenButton = requireActivity().findViewById(R.id.exo_fullscreen);
|
|
||||||
if (fullscreen) {
|
|
||||||
fullscreenButton.setText(R.string.video_compress_icon);
|
|
||||||
} else {
|
|
||||||
fullscreenButton.setText(R.string.video_expand_icon);
|
|
||||||
}
|
|
||||||
new Iconics.Builder().on(fullscreenButton).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsFullscreen() {
|
|
||||||
return isFullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fullScreenToggle() {
|
|
||||||
if (!isFullscreen) {
|
|
||||||
setIsFullscreen(true);
|
|
||||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
||||||
} else {
|
|
||||||
setIsFullscreen(false);
|
|
||||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Torrent Playback
|
|
||||||
*
|
|
||||||
* @return torrent stream
|
|
||||||
*/
|
|
||||||
private TorrentStream setupTorrentStream() {
|
|
||||||
|
|
||||||
TorrentOptions torrentOptions = new TorrentOptions.Builder()
|
|
||||||
.saveLocation(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
|
|
||||||
.removeFilesAfterStop(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
TorrentStream torrentStream = TorrentStream.init(torrentOptions);
|
|
||||||
|
|
||||||
torrentStream.addListener(new TorrentListener() {
|
|
||||||
@Override
|
|
||||||
public void onStreamReady(Torrent torrent) {
|
|
||||||
String videopath = Uri.fromFile(torrent.getVideoFile()).toString();
|
|
||||||
Log.d(TAG, "Ready! torrentStream videopath:" + videopath);
|
|
||||||
mService.setCurrentStreamUrl(videopath, false);
|
|
||||||
startPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamProgress(Torrent torrent, StreamStatus streamStatus) {
|
|
||||||
if (streamStatus.bufferProgress <= 100 && progressBar.getProgress() < 100 && progressBar.getProgress() != streamStatus.bufferProgress) {
|
|
||||||
//Log.d(TAG, "Progress: " + streamStatus.bufferProgress);
|
|
||||||
progressBar.setProgress(streamStatus.bufferProgress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamStopped() {
|
|
||||||
Log.d(TAG, "Stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamPrepared(Torrent torrent) {
|
|
||||||
Log.d(TAG, "Prepared");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamStarted(Torrent torrent) {
|
|
||||||
Log.d(TAG, "Started");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamError(Torrent torrent, Exception e) {
|
|
||||||
Log.d(TAG, "Error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return torrentStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoEnabled(DecoderCounters counters) {
|
|
||||||
Log.v(TAG, "onVideoEnabled()...");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoInputFormatChanged(Format format) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDroppedFrames(int count, long elapsedMs) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRenderedFirstFrame(Surface surface) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoDisabled(DecoderCounters counters) {
|
|
||||||
Log.v(TAG, "onVideoDisabled()...");
|
|
||||||
}
|
|
||||||
|
|
||||||
View.OnTouchListener touchListener = new View.OnTouchListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
return mDetector.onTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
public String getVideoUuid() {
|
|
||||||
return mVideoUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
|
|
||||||
/*
|
|
||||||
@Override
|
|
||||||
public boolean onDown(MotionEvent event) {
|
|
||||||
Log.d("TAG","onDown: ");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
|
||||||
Log.i("TAG", "onSingleTapConfirmed: ");
|
|
||||||
pauseToggle();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLongPress(MotionEvent e) {
|
|
||||||
Log.i("TAG", "onLongPress: ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDoubleTap(MotionEvent e) {
|
|
||||||
Log.i("TAG", "onDoubleTap: ");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onScroll(MotionEvent e1, MotionEvent e2,
|
|
||||||
float distanceX, float distanceY) {
|
|
||||||
Log.i("TAG", "onScroll: ");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
@Override
|
|
||||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
|
||||||
float velocityX, float velocityY) {
|
|
||||||
Log.d(TAG, event1.toString());
|
|
||||||
Log.d(TAG, event2.toString());
|
|
||||||
Log.d(TAG, String.valueOf(velocityX));
|
|
||||||
Log.d(TAG, String.valueOf(velocityY));
|
|
||||||
//arbitrarily velocity speeds that seem to work to differentiate events.
|
|
||||||
if (velocityY > 4000) {
|
|
||||||
Log.d(TAG, "we have a drag down event");
|
|
||||||
if (canEnterPipMode(getContext())) {
|
|
||||||
requireActivity().enterPictureInPictureMode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((velocityX > 2000) && (Math.abs(velocityY) < 2000)) {
|
|
||||||
Log.d(TAG, "swipe right " + velocityY);
|
|
||||||
}
|
|
||||||
if ((velocityX < 2000) && (Math.abs(velocityY) < 2000)) {
|
|
||||||
Log.d(TAG, "swipe left " + velocityY);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,496 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.fragment
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.video.VideoRendererEventListener
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerView
|
||||||
|
import android.content.Intent
|
||||||
|
import net.schueller.peertube.service.VideoPlayerService
|
||||||
|
import com.github.se_bastiaan.torrentstream.TorrentStream
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.os.IBinder
|
||||||
|
import net.schueller.peertube.service.VideoPlayerService.LocalBinder
|
||||||
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.AspectRatioListener
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.os.Bundle
|
||||||
|
import net.schueller.peertube.R
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.PictureInPictureParams
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import net.schueller.peertube.helper.APIUrlHelper
|
||||||
|
import net.schueller.peertube.network.GetVideoDataService
|
||||||
|
import net.schueller.peertube.network.RetrofitInstance
|
||||||
|
import android.widget.Toast
|
||||||
|
import net.schueller.peertube.helper.ErrorHelper
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener
|
||||||
|
import com.github.se_bastiaan.torrentstream.Torrent
|
||||||
|
import com.github.se_bastiaan.torrentstream.StreamStatus
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderCounters
|
||||||
|
import android.view.View.OnTouchListener
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import android.os.Build.VERSION_CODES
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.github.se_bastiaan.torrentstream.TorrentOptions.Builder
|
||||||
|
import com.google.android.exoplayer2.Format
|
||||||
|
import com.google.android.exoplayer2.util.Util
|
||||||
|
import com.mikepenz.iconics.Iconics
|
||||||
|
import net.schueller.peertube.R.layout
|
||||||
|
import net.schueller.peertube.R.string
|
||||||
|
import net.schueller.peertube.helper.VideoHelper
|
||||||
|
import net.schueller.peertube.model.Video
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.lang.Exception
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class VideoPlayerFragment : Fragment(), VideoRendererEventListener {
|
||||||
|
|
||||||
|
var videoUuid: String? = null
|
||||||
|
private set
|
||||||
|
// private var progressBar: ProgressBar? = null
|
||||||
|
private var exoPlayer: PlayerView? = null
|
||||||
|
private var videoPlayerIntent: Intent? = null
|
||||||
|
private var mBound = false
|
||||||
|
private var isFullscreen = false
|
||||||
|
private var mService: VideoPlayerService? = null
|
||||||
|
private var torrentStream: TorrentStream? = null
|
||||||
|
// private var torrentStatus: LinearLayout? = null
|
||||||
|
var videoAspectRatio = 0f
|
||||||
|
private set
|
||||||
|
private var mDetector: GestureDetector? = null
|
||||||
|
private val mConnection: ServiceConnection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
|
Log.d(TAG, "onServiceConnected")
|
||||||
|
val binder = service as LocalBinder
|
||||||
|
mService = binder.service
|
||||||
|
|
||||||
|
// 2. Create the player
|
||||||
|
exoPlayer!!.player = mService!!.player
|
||||||
|
mBound = true
|
||||||
|
loadVideo()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(componentName: ComponentName) {
|
||||||
|
Log.d(TAG, "onServiceDisconnected")
|
||||||
|
exoPlayer!!.player = null
|
||||||
|
mBound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val aspectRatioListener: AspectRatioListener = AspectRatioListener {
|
||||||
|
targetAspectRatio, _, _ -> videoAspectRatio = targetAspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
return inflater.inflate(layout.fragment_video_player, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start(videoUuid: String?) {
|
||||||
|
|
||||||
|
// start service
|
||||||
|
val context = context
|
||||||
|
val activity: Activity? = activity
|
||||||
|
this.videoUuid = videoUuid
|
||||||
|
assert(activity != null)
|
||||||
|
// progressBar = activity?.findViewById(R.id.torrent_progress)
|
||||||
|
// progressBar?.max = 100
|
||||||
|
assert(context != null)
|
||||||
|
exoPlayer = PlayerView(context!!)
|
||||||
|
exoPlayer = activity?.findViewById(R.id.video_view)
|
||||||
|
exoPlayer?.controllerShowTimeoutMs = 1000
|
||||||
|
exoPlayer?.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
|
mDetector = GestureDetector(context, MyGestureListener())
|
||||||
|
exoPlayer?.setOnTouchListener(touchListener)
|
||||||
|
exoPlayer?.setAspectRatioListener(aspectRatioListener)
|
||||||
|
// torrentStatus = activity?.findViewById(R.id.exo_torrent_status)
|
||||||
|
|
||||||
|
// Full screen Icon
|
||||||
|
val fullscreenText = activity?.findViewById<TextView>(R.id.exo_fullscreen)
|
||||||
|
val fullscreenButton = activity?.findViewById<FrameLayout>(R.id.exo_fullscreen_button)
|
||||||
|
fullscreenText?.setText(string.video_expand_icon)
|
||||||
|
if (fullscreenText != null) {
|
||||||
|
Iconics.Builder().on(fullscreenText).build()
|
||||||
|
fullscreenButton?.setOnClickListener {
|
||||||
|
Log.d(TAG, "Fullscreen")
|
||||||
|
fullScreenToggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mBound) {
|
||||||
|
videoPlayerIntent = Intent(context, VideoPlayerService::class.java)
|
||||||
|
activity?.bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadVideo() {
|
||||||
|
val context = context
|
||||||
|
|
||||||
|
// get video details from api
|
||||||
|
val apiBaseURL = APIUrlHelper.getUrlWithVersion(context)
|
||||||
|
val service =
|
||||||
|
RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(
|
||||||
|
GetVideoDataService::class.java
|
||||||
|
)
|
||||||
|
val call = service.getVideoData(videoUuid)
|
||||||
|
call.enqueue(object : Callback<Video?> {
|
||||||
|
override fun onResponse(call: Call<Video?>, response: Response<Video?>) {
|
||||||
|
val video = response.body()
|
||||||
|
mService!!.setCurrentVideo(video)
|
||||||
|
if (video == null) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Unable to retrieve video information, try again later.",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
playVideo(video)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Video?>, t: Throwable) {
|
||||||
|
Log.wtf(TAG, t.fillInStackTrace())
|
||||||
|
ErrorHelper.showToastFromCommunicationError(activity, t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun useController(value: Boolean) {
|
||||||
|
if (mBound) {
|
||||||
|
exoPlayer!!.useController = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playVideo(video: Video) {
|
||||||
|
val context = context
|
||||||
|
|
||||||
|
// video Meta fragment
|
||||||
|
val videoMetaDataFragment =
|
||||||
|
(requireActivity().supportFragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment?)!!
|
||||||
|
videoMetaDataFragment.updateVideoMeta(video, mService)
|
||||||
|
|
||||||
|
val sharedPref = context?.getSharedPreferences(
|
||||||
|
context.packageName + "_preferences",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
var prefTorrentPlayer = false
|
||||||
|
var videoQuality = 999999
|
||||||
|
if (sharedPref != null) {
|
||||||
|
prefTorrentPlayer = sharedPref.getBoolean(getString(string.pref_torrent_player_key), false)
|
||||||
|
videoQuality = sharedPref.getInt(getString(string.pref_quality_key), 999999)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (prefTorrentPlayer) {
|
||||||
|
// torrentStatus!!.visibility = View.VISIBLE
|
||||||
|
// val stream = video.files[0].torrentUrl
|
||||||
|
// Log.v(TAG, "getTorrentUrl : " + video.files[0].torrentUrl)
|
||||||
|
// torrentStream = setupTorrentStream()
|
||||||
|
// torrentStream!!.startStream(stream)
|
||||||
|
// } else {
|
||||||
|
var urlToPlay: String? = null
|
||||||
|
var isHLS = false
|
||||||
|
|
||||||
|
// try HLS stream first
|
||||||
|
// get video qualities
|
||||||
|
// TODO: if auto is set all versions except 0p should be added to a track and have exoplayer auto select optimal bitrate
|
||||||
|
if (video.streamingPlaylists.size > 0) {
|
||||||
|
urlToPlay = video.streamingPlaylists[0].playlistUrl
|
||||||
|
isHLS = true
|
||||||
|
} else {
|
||||||
|
if (video.files.size > 0) {
|
||||||
|
urlToPlay = video.files[0].fileUrl // default, take first found, usually highest res
|
||||||
|
for (file in video.files) {
|
||||||
|
// Set quality if it matches
|
||||||
|
if (file.resolution.id == videoQuality) {
|
||||||
|
urlToPlay = file.fileUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (urlToPlay!!.isNotEmpty()) {
|
||||||
|
mService!!.setCurrentStreamUrl(urlToPlay, isHLS)
|
||||||
|
// torrentStatus!!.visibility = View.GONE
|
||||||
|
startPlayer()
|
||||||
|
} else {
|
||||||
|
stopVideo()
|
||||||
|
Toast.makeText(context, string.api_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
Log.v(TAG, "end of load Video")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPlayer() {
|
||||||
|
Util.startForegroundService(requireContext(), videoPlayerIntent!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroyVideo() {
|
||||||
|
exoPlayer!!.player = null
|
||||||
|
if (torrentStream != null) {
|
||||||
|
torrentStream!!.stopStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pauseVideo() {
|
||||||
|
if (mBound) {
|
||||||
|
mService!!.player!!.playWhenReady = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun pauseToggle() {
|
||||||
|
// if (mBound) {
|
||||||
|
// mService!!.player!!.playWhenReady = !mService!!.player!!.playWhenReady
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun unPauseVideo() {
|
||||||
|
if (mBound) {
|
||||||
|
mService!!.player!!.playWhenReady = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isPaused: Boolean
|
||||||
|
get() = !mService!!.player!!.playWhenReady
|
||||||
|
|
||||||
|
fun showControls(value: Boolean) {
|
||||||
|
exoPlayer!!.useController = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopVideo() {
|
||||||
|
if (mBound) {
|
||||||
|
requireContext().unbindService(mConnection)
|
||||||
|
mBound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* triggered rotation and button press
|
||||||
|
*/
|
||||||
|
fun setIsFullscreen(fullscreen: Boolean) {
|
||||||
|
isFullscreen = fullscreen
|
||||||
|
val fullscreenButton = requireActivity().findViewById<TextView>(R.id.exo_fullscreen)
|
||||||
|
if (fullscreen) {
|
||||||
|
hideSystemBars()
|
||||||
|
fullscreenButton.setText(string.video_compress_icon)
|
||||||
|
} else {
|
||||||
|
restoreSystemBars()
|
||||||
|
fullscreenButton.setText(string.video_expand_icon)
|
||||||
|
}
|
||||||
|
Iconics.Builder().on(fullscreenButton).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideSystemBars()
|
||||||
|
{
|
||||||
|
val view = this.view
|
||||||
|
if (view != null) {
|
||||||
|
val windowInsetsController =
|
||||||
|
ViewCompat.getWindowInsetsController(view) ?: return
|
||||||
|
// Configure the behavior of the hidden system bars
|
||||||
|
windowInsetsController.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
// Hide both the status bar and the navigation bar
|
||||||
|
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreSystemBars()
|
||||||
|
{
|
||||||
|
val view = this.view
|
||||||
|
if (view != null) {
|
||||||
|
val windowInsetsController =
|
||||||
|
ViewCompat.getWindowInsetsController(view) ?: return
|
||||||
|
// Show both the status bar and the navigation bar
|
||||||
|
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIsFullscreen(): Boolean {
|
||||||
|
return isFullscreen
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered by button press
|
||||||
|
*/
|
||||||
|
fun fullScreenToggle() {
|
||||||
|
if (!isFullscreen) {
|
||||||
|
setIsFullscreen(true)
|
||||||
|
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||||
|
} else {
|
||||||
|
setIsFullscreen(false)
|
||||||
|
// we want to force portrait if fullscreen is switched of as we do not have a min. landscape view
|
||||||
|
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Torrent Playback
|
||||||
|
// *
|
||||||
|
// * @return torrent stream
|
||||||
|
// */
|
||||||
|
// private fun setupTorrentStream(): TorrentStream {
|
||||||
|
// val torrentOptions = Builder()
|
||||||
|
// .saveLocation(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
|
||||||
|
// .removeFilesAfterStop(true)
|
||||||
|
// .build()
|
||||||
|
// val torrentStream = TorrentStream.init(torrentOptions)
|
||||||
|
// torrentStream.addListener(object : TorrentListener {
|
||||||
|
// override fun onStreamReady(torrent: Torrent) {
|
||||||
|
// val videoPath = Uri.fromFile(torrent.videoFile).toString()
|
||||||
|
// Log.d(TAG, "Ready! torrentStream videoPath:$videoPath")
|
||||||
|
// mService!!.setCurrentStreamUrl(videoPath, false)
|
||||||
|
// startPlayer()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onStreamProgress(torrent: Torrent, streamStatus: StreamStatus) {
|
||||||
|
// if (streamStatus.bufferProgress <= 100 && progressBar!!.progress < 100 && progressBar!!.progress != streamStatus.bufferProgress) {
|
||||||
|
// //Log.d(TAG, "Progress: " + streamStatus.bufferProgress);
|
||||||
|
// progressBar!!.progress = streamStatus.bufferProgress
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onStreamStopped() {
|
||||||
|
// Log.d(TAG, "Stopped")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onStreamPrepared(torrent: Torrent) {
|
||||||
|
// Log.d(TAG, "Prepared")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onStreamStarted(torrent: Torrent) {
|
||||||
|
// Log.d(TAG, "Started")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onStreamError(torrent: Torrent, e: Exception) {
|
||||||
|
// Log.d(TAG, "Error: " + e.message)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// return torrentStream
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun onVideoEnabled(counters: DecoderCounters) {
|
||||||
|
Log.v(TAG, "onVideoEnabled()...")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onVideoDecoderInitialized(
|
||||||
|
decoderName: String,
|
||||||
|
initializedTimestampMs: Long,
|
||||||
|
initializationDurationMs: Long
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onVideoInputFormatChanged(format: Format) {}
|
||||||
|
override fun onDroppedFrames(count: Int, elapsedMs: Long) {}
|
||||||
|
override fun onVideoDisabled(counters: DecoderCounters) {
|
||||||
|
Log.v(TAG, "onVideoDisabled()...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// touch event on video player
|
||||||
|
private var touchListener = OnTouchListener { _, event ->
|
||||||
|
//v.performClick() // causes flicker but should be implemented for accessibility
|
||||||
|
mDetector!!.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inner class MyGestureListener : SimpleOnGestureListener() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
@Override
|
||||||
|
public boolean onDown(MotionEvent event) {
|
||||||
|
Log.d("TAG","onDown: ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||||
|
Log.i("TAG", "onSingleTapConfirmed: ");
|
||||||
|
pauseToggle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongPress(MotionEvent e) {
|
||||||
|
Log.i("TAG", "onLongPress: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDoubleTap(MotionEvent e) {
|
||||||
|
Log.i("TAG", "onDoubleTap: ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(MotionEvent e1, MotionEvent e2,
|
||||||
|
float distanceX, float distanceY) {
|
||||||
|
Log.i("TAG", "onScroll: ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = VERSION_CODES.N)
|
||||||
|
override fun onFling(
|
||||||
|
event1: MotionEvent, event2: MotionEvent,
|
||||||
|
velocityX: Float, velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
Log.d(TAG, event1.toString())
|
||||||
|
Log.d(TAG, event2.toString())
|
||||||
|
Log.d(TAG, velocityX.toString())
|
||||||
|
Log.d(TAG, velocityY.toString())
|
||||||
|
//arbitrarily velocity speeds that seem to work to differentiate events.
|
||||||
|
if (velocityY > 4000) {
|
||||||
|
Log.d(TAG, "we have a drag down event")
|
||||||
|
if (VideoHelper.canEnterPipMode(context)) {
|
||||||
|
if (Build.VERSION.SDK_INT >= VERSION_CODES.O) {
|
||||||
|
val pipParams = PictureInPictureParams.Builder()
|
||||||
|
requireActivity().enterPictureInPictureMode(pipParams.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (velocityX > 2000 && abs(velocityY) < 2000) {
|
||||||
|
Log.d(TAG, "swipe right $velocityY")
|
||||||
|
}
|
||||||
|
if (velocityX < 2000 && abs(velocityY) < 2000) {
|
||||||
|
Log.d(TAG, "swipe left $velocityY")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val TAG = "VideoPlayerFragment"
|
||||||
|
}
|
||||||
|
}
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package net.schueller.peertube.intents;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
import android.webkit.URLUtil;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
|
|
||||||
import com.github.se_bastiaan.torrentstream.TorrentOptions;
|
|
||||||
|
|
||||||
import net.schueller.peertube.R;
|
|
||||||
import net.schueller.peertube.helper.APIUrlHelper;
|
|
||||||
import net.schueller.peertube.model.Video;
|
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
|
|
||||||
|
|
||||||
public class Intents {
|
|
||||||
|
|
||||||
private static final String TAG = "Intents";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
|
|
||||||
*
|
|
||||||
* @param context context
|
|
||||||
* @param video video
|
|
||||||
*/
|
|
||||||
// TODO, offer which version to download
|
|
||||||
public static void Share(Context context, Video video) {
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(Intent.ACTION_SEND);
|
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, video.getName());
|
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, APIUrlHelper.getShareUrl(context, video.getUuid()) );
|
|
||||||
intent.setType("text/plain");
|
|
||||||
|
|
||||||
context.startActivity(intent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param context context
|
|
||||||
* @param video video
|
|
||||||
*/
|
|
||||||
// TODO, offer which version to download
|
|
||||||
public static void Download(Context context, Video video) {
|
|
||||||
|
|
||||||
if (video.getFiles().size() > 0)
|
|
||||||
{
|
|
||||||
String url = video.getFiles().get( 0 ).getFileDownloadUrl();
|
|
||||||
// make sure it is a valid filename
|
|
||||||
String destFilename = video.getName().replaceAll( "[^a-zA-Z0-9]", "_" ) + "." + MimeTypeMap.getFileExtensionFromUrl( URLUtil.guessFileName( url, null, null ) );
|
|
||||||
|
|
||||||
//Toast.makeText(context, destFilename, Toast.LENGTH_LONG).show();
|
|
||||||
DownloadManager.Request request = new DownloadManager.Request( Uri.parse( url ) );
|
|
||||||
request.setDescription( video.getDescription() );
|
|
||||||
request.setTitle( video.getName() );
|
|
||||||
request.allowScanningByMediaScanner();
|
|
||||||
request.setNotificationVisibility( DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED );
|
|
||||||
request.setDestinationInExternalPublicDir( Environment.DIRECTORY_DOWNLOADS, destFilename );
|
|
||||||
|
|
||||||
// get download service and enqueue file
|
|
||||||
DownloadManager manager = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE );
|
|
||||||
manager.enqueue( request );
|
|
||||||
} else {
|
|
||||||
Toast.makeText( context, R.string.api_error, Toast.LENGTH_LONG ).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
146
app/src/main/java/net/schueller/peertube/intents/Intents.kt
Normal file
146
app/src/main/java/net/schueller/peertube/intents/Intents.kt
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.intents
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import net.schueller.peertube.helper.APIUrlHelper
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import android.os.Environment
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import net.schueller.peertube.R
|
||||||
|
import net.schueller.peertube.model.Video
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
object Intents {
|
||||||
|
private const val TAG = "Intents"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://troll.tv/videos/watch/6edbd9d1-e3c5-4a6c-8491-646e2020469c
|
||||||
|
*
|
||||||
|
* @param context context
|
||||||
|
* @param video video
|
||||||
|
*/
|
||||||
|
// TODO, offer which version to download
|
||||||
|
@JvmStatic
|
||||||
|
fun Share(context: Context, video: Video) {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.action = Intent.ACTION_SEND
|
||||||
|
intent.putExtra(Intent.EXTRA_SUBJECT, video.name)
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, APIUrlHelper.getShareUrl(context, video.uuid))
|
||||||
|
intent.type = "text/plain"
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context context
|
||||||
|
* @param video video
|
||||||
|
*/
|
||||||
|
// TODO, offer which version to download
|
||||||
|
fun Download(context: Context, video: Video) {
|
||||||
|
|
||||||
|
// deal withe permissions here
|
||||||
|
|
||||||
|
// get permission to store file
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
val activity = getActivity(context)
|
||||||
|
|
||||||
|
if (activity != null) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
startDownload(video, context)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.video_download_permission_error),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
startDownload(video, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDownload(video: Video, context: Context)
|
||||||
|
{
|
||||||
|
if (video.files.size > 0) {
|
||||||
|
val url = video.files[0].fileDownloadUrl
|
||||||
|
// make sure it is a valid filename
|
||||||
|
val destFilename = video.name.replace(
|
||||||
|
"[^a-zA-Z0-9]".toRegex(),
|
||||||
|
"_"
|
||||||
|
) + "." + MimeTypeMap.getFileExtensionFromUrl(
|
||||||
|
URLUtil.guessFileName(url, null, null)
|
||||||
|
)
|
||||||
|
|
||||||
|
//Toast.makeText(context, destFilename, Toast.LENGTH_LONG).show();
|
||||||
|
val request = DownloadManager.Request(Uri.parse(url))
|
||||||
|
request.setDescription(video.description)
|
||||||
|
request.setTitle(video.name)
|
||||||
|
request.allowScanningByMediaScanner()
|
||||||
|
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, destFilename)
|
||||||
|
|
||||||
|
// get download service and enqueue file
|
||||||
|
val manager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
manager.enqueue(request)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, R.string.api_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getActivity(context: Context?): Activity? {
|
||||||
|
if (context == null) {
|
||||||
|
return null
|
||||||
|
} else if (context is ContextWrapper) {
|
||||||
|
return if (context is Activity) {
|
||||||
|
context
|
||||||
|
} else {
|
||||||
|
getActivity(context.baseContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
38
app/src/main/java/net/schueller/peertube/model/Comment.kt
Normal file
38
app/src/main/java/net/schueller/peertube/model/Comment.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.model
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(
|
||||||
|
|
||||||
|
val id: Int,
|
||||||
|
val url: String,
|
||||||
|
val text: String,
|
||||||
|
val threadId: Int,
|
||||||
|
val inReplyToCommentId: Int? = null,
|
||||||
|
val videoId: Int,
|
||||||
|
val createdAt: Date,
|
||||||
|
val updatedAt: Date,
|
||||||
|
val deletedAt: Date? = null,
|
||||||
|
val isDeleted: Boolean,
|
||||||
|
val totalRepliesFromVideoAuthor: Int,
|
||||||
|
val totalReplies: Int,
|
||||||
|
val account: Account
|
||||||
|
|
||||||
|
)
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import net.schueller.peertube.model.ui.OverviewRecycleViewItem
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
class CommentThread(
|
||||||
|
val total: Int,
|
||||||
|
val totalNotDeletedComments: Int,
|
||||||
|
@SerializedName("data")
|
||||||
|
val comments: ArrayList<Comment>
|
||||||
|
|
||||||
|
): OverviewRecycleViewItem()
|
@ -35,7 +35,7 @@ class Video(
|
|||||||
var licence: Licence,
|
var licence: Licence,
|
||||||
var language: Language,
|
var language: Language,
|
||||||
var nsfw: Boolean,
|
var nsfw: Boolean,
|
||||||
var description: String,
|
var description: String? = null,
|
||||||
var local: Boolean,
|
var local: Boolean,
|
||||||
var live: Boolean,
|
var live: Boolean,
|
||||||
var duration: Int,
|
var duration: Int,
|
||||||
@ -66,7 +66,7 @@ class Video(
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getMediaDescription(context: Context?, video: Video): MediaDescriptionCompat {
|
fun getMediaDescription(video: Video): MediaDescriptionCompat {
|
||||||
|
|
||||||
// String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
// String apiBaseURL = APIUrlHelper.getUrlWithVersion(context);
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.schueller.peertube.model.ui
|
||||||
|
|
||||||
|
import net.schueller.peertube.model.Video
|
||||||
|
|
||||||
|
class VideoMetaViewItem: OverviewRecycleViewItem() {
|
||||||
|
var video: Video? = null
|
||||||
|
}
|
@ -16,15 +16,23 @@
|
|||||||
*/
|
*/
|
||||||
package net.schueller.peertube.network;
|
package net.schueller.peertube.network;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
import net.schueller.peertube.model.Account;
|
import net.schueller.peertube.model.Account;
|
||||||
import net.schueller.peertube.model.Channel;
|
import net.schueller.peertube.model.Channel;
|
||||||
import net.schueller.peertube.model.ChannelList;
|
import net.schueller.peertube.model.ChannelList;
|
||||||
import net.schueller.peertube.model.Me;
|
import net.schueller.peertube.model.Me;
|
||||||
import net.schueller.peertube.model.VideoList;
|
import net.schueller.peertube.model.VideoList;
|
||||||
|
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.DELETE;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Header;
|
import retrofit2.http.Header;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.PUT;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
@ -52,5 +60,18 @@ public interface GetUserService {
|
|||||||
@Path(value = "displayName", encoded = true) String displayName
|
@Path(value = "displayName", encoded = true) String displayName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@GET("users/me/subscriptions/exist")
|
||||||
|
Call<JsonObject> subscriptionsExist(
|
||||||
|
@Query("uris") String videoChannelNameAndHost
|
||||||
|
);
|
||||||
|
|
||||||
|
@POST("users/me/subscriptions")
|
||||||
|
Call<ResponseBody> subscribe(
|
||||||
|
@Body RequestBody params
|
||||||
|
);
|
||||||
|
|
||||||
|
@DELETE("users/me/subscriptions/{videoChannelNameAndHost}")
|
||||||
|
Call<ResponseBody> unsubscribe(
|
||||||
|
@Path(value = "videoChannelNameAndHost", encoded = true) String videoChannelNameAndHost
|
||||||
|
);
|
||||||
}
|
}
|
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.schueller.peertube.network;
|
package net.schueller.peertube.network;
|
||||||
|
|
||||||
|
import net.schueller.peertube.model.CommentThread;
|
||||||
import net.schueller.peertube.model.Description;
|
import net.schueller.peertube.model.Description;
|
||||||
import net.schueller.peertube.model.Overview;
|
import net.schueller.peertube.model.Overview;
|
||||||
import net.schueller.peertube.model.Rating;
|
import net.schueller.peertube.model.Rating;
|
||||||
@ -91,4 +92,12 @@ public interface GetVideoDataService {
|
|||||||
@Query("page") int page
|
@Query("page") int page
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// https://troll.tv/api/v1/videos/{id}/comment-threads?start=0&count=10&sort=-createdAt
|
||||||
|
@GET("videos/{id}/comment-threads")
|
||||||
|
Call<CommentThread> getCommentThreads(
|
||||||
|
@Path(value = "id", encoded = true) Integer id,
|
||||||
|
@Query("start") int start,
|
||||||
|
@Query("count") int count,
|
||||||
|
@Query("sort") String sort
|
||||||
|
);
|
||||||
}
|
}
|
@ -1,358 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package net.schueller.peertube.service;
|
|
||||||
|
|
||||||
import static android.media.session.PlaybackState.ACTION_PAUSE;
|
|
||||||
import static android.media.session.PlaybackState.ACTION_PLAY;
|
|
||||||
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
|
|
||||||
import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID;
|
|
||||||
import static net.schueller.peertube.network.UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.media.MediaDescriptionCompat;
|
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.URLUtil;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
|
||||||
import com.google.android.exoplayer2.Player;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
|
||||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
|
||||||
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
|
|
||||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
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.model.Video;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
public class VideoPlayerService extends Service {
|
|
||||||
|
|
||||||
private static final String TAG = "VideoPlayerService";
|
|
||||||
|
|
||||||
private static final String MEDIA_SESSION_TAG = "peertube_player";
|
|
||||||
|
|
||||||
private final IBinder mBinder = new LocalBinder();
|
|
||||||
|
|
||||||
private static final String PLAYBACK_CHANNEL_ID = "playback_channel";
|
|
||||||
|
|
||||||
private static final Integer PLAYBACK_NOTIFICATION_ID = 1;
|
|
||||||
|
|
||||||
public SimpleExoPlayer player;
|
|
||||||
|
|
||||||
private Video currentVideo;
|
|
||||||
|
|
||||||
private String currentStreamUrl;
|
|
||||||
|
|
||||||
private boolean currentStreamUrlIsHLS;
|
|
||||||
|
|
||||||
private PlayerNotificationManager playerNotificationManager;
|
|
||||||
|
|
||||||
private IntentFilter becomeNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
|
||||||
|
|
||||||
private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
Log.v(TAG, "onCreate...");
|
|
||||||
|
|
||||||
super.onCreate();
|
|
||||||
|
|
||||||
player = new SimpleExoPlayer.Builder(getApplicationContext())
|
|
||||||
.setTrackSelector(new DefaultTrackSelector(getApplicationContext()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Stop player if audio device changes, e.g. headphones unplugged
|
|
||||||
player.addListener(new Player.EventListener() {
|
|
||||||
@Override
|
|
||||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
|
||||||
|
|
||||||
if (playbackState == ACTION_PAUSE) { // this means that pause is available, hence the audio is playing
|
|
||||||
Log.v(TAG, "ACTION_PLAY: " + playbackState);
|
|
||||||
registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playbackState
|
|
||||||
== ACTION_PLAY) { // this means that play is available, hence the audio is paused or stopped
|
|
||||||
Log.v(TAG, "ACTION_PAUSE: " + playbackState);
|
|
||||||
safeUnregisterReceiver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LocalBinder extends Binder {
|
|
||||||
|
|
||||||
public VideoPlayerService getService() {
|
|
||||||
// Return this instance of VideoPlayerService so clients can call public methods
|
|
||||||
return VideoPlayerService.this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
|
|
||||||
Log.v(TAG, "onDestroy...");
|
|
||||||
if (playerNotificationManager != null) {
|
|
||||||
playerNotificationManager.setPlayer(null);
|
|
||||||
}
|
|
||||||
//Was seeing an error when exiting the program about not unregistering the receiver.
|
|
||||||
safeUnregisterReceiver();
|
|
||||||
|
|
||||||
if (player != null) {
|
|
||||||
player.release();
|
|
||||||
player = null;
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void safeUnregisterReceiver()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
unregisterReceiver(myNoisyAudioStreamReceiver);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return mBinder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
Context context = this;
|
|
||||||
Log.v(TAG, "onStartCommand...");
|
|
||||||
|
|
||||||
if (!URLUtil.isValidUrl(currentStreamUrl)) {
|
|
||||||
Toast.makeText(context, "Invalid URL provided. Unable to play video.", Toast.LENGTH_SHORT).show();
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
} else {
|
|
||||||
playVideo();
|
|
||||||
return START_STICKY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setCurrentVideo(Video video) {
|
|
||||||
Log.v(TAG, "setCurrentVideo...");
|
|
||||||
currentVideo = video;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentStreamUrl(String streamUrl, boolean isHLS) {
|
|
||||||
Log.v(TAG, "setCurrentStreamUrl..." + streamUrl);
|
|
||||||
currentStreamUrlIsHLS = isHLS;
|
|
||||||
currentStreamUrl = streamUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Playback speed control
|
|
||||||
public void setPlayBackSpeed(float speed) {
|
|
||||||
Log.v(TAG, "setPlayBackSpeed...");
|
|
||||||
player.setPlaybackParameters(new PlaybackParameters(speed));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current playback speed of the player.
|
|
||||||
*
|
|
||||||
* @return the current playback speed of the player.
|
|
||||||
*/
|
|
||||||
public float getPlayBackSpeed() {
|
|
||||||
return player.getPlaybackParameters().speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playVideo() {
|
|
||||||
Context context = this;
|
|
||||||
|
|
||||||
// We need a valid URL
|
|
||||||
|
|
||||||
Log.v(TAG, "playVideo...");
|
|
||||||
|
|
||||||
// Produces DataSource instances through which media data is loaded.
|
|
||||||
// DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
|
|
||||||
// Util.getUserAgent(getApplicationContext(), "PeerTube"), null);
|
|
||||||
|
|
||||||
OkHttpClient.Builder okhttpClientBuilder;
|
|
||||||
|
|
||||||
if (!APIUrlHelper.useInsecureConnection(this)) {
|
|
||||||
okhttpClientBuilder = new OkHttpClient.Builder();
|
|
||||||
} else {
|
|
||||||
okhttpClientBuilder = getUnsafeOkHttpClientBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a data source factory.
|
|
||||||
DataSource.Factory dataSourceFactory = new OkHttpDataSourceFactory(okhttpClientBuilder.build(), Util.getUserAgent(getApplicationContext(), "PeerTube"));
|
|
||||||
|
|
||||||
// Create a progressive media source pointing to a stream uri.
|
|
||||||
MediaSource mediaSource;
|
|
||||||
if (currentStreamUrlIsHLS) {
|
|
||||||
mediaSource = new HlsMediaSource.Factory(dataSourceFactory)
|
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)));
|
|
||||||
} else {
|
|
||||||
mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
|
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the media source to be played.
|
|
||||||
player.setMediaSource(mediaSource);
|
|
||||||
|
|
||||||
// Prepare the player.
|
|
||||||
player.prepare();
|
|
||||||
|
|
||||||
// Auto play
|
|
||||||
player.setPlayWhenReady(true);
|
|
||||||
|
|
||||||
//set playback speed to global default
|
|
||||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
|
||||||
float speed = Float.parseFloat(sharedPref.getString(getString(R.string.pref_video_speed_key), "1.0"));
|
|
||||||
|
|
||||||
this.setPlayBackSpeed(speed);
|
|
||||||
|
|
||||||
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
|
|
||||||
context, PLAYBACK_CHANNEL_ID, R.string.playback_channel_name,
|
|
||||||
PLAYBACK_NOTIFICATION_ID,
|
|
||||||
new PlayerNotificationManager.MediaDescriptionAdapter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCurrentContentTitle(Player player) {
|
|
||||||
return currentVideo.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public PendingIntent createCurrentContentIntent(Player player) {
|
|
||||||
Intent intent = new Intent(context, VideoPlayActivity.class);
|
|
||||||
intent.putExtra(EXTRA_VIDEOID, currentVideo.getUuid());
|
|
||||||
return PendingIntent.getActivity(context, 0, intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCurrentContentText(Player player) {
|
|
||||||
return MetaDataHelper.getMetaString(
|
|
||||||
currentVideo.getCreatedAt(),
|
|
||||||
currentVideo.getViews(),
|
|
||||||
getBaseContext()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Bitmap getCurrentLargeIcon(Player player,
|
|
||||||
PlayerNotificationManager.BitmapCallback callback) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
playerNotificationManager.setSmallIcon(R.drawable.ic_logo_bw);
|
|
||||||
|
|
||||||
// don't show skip buttons in notification
|
|
||||||
playerNotificationManager.setUseNavigationActions(false);
|
|
||||||
playerNotificationManager.setUseStopAction(true);
|
|
||||||
|
|
||||||
playerNotificationManager.setNotificationListener(
|
|
||||||
new PlayerNotificationManager.NotificationListener() {
|
|
||||||
@Override
|
|
||||||
public void onNotificationStarted(int notificationId, Notification notification) {
|
|
||||||
startForeground(notificationId, notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNotificationCancelled(int notificationId) {
|
|
||||||
Log.v(TAG, "onNotificationCancelled...");
|
|
||||||
stopForeground(true);
|
|
||||||
Intent killFloat = new Intent(ACTION_STOP);
|
|
||||||
sendBroadcast(killFloat);
|
|
||||||
/*
|
|
||||||
Intent killFloat = new Intent(BROADCAST_ACTION);
|
|
||||||
Intent killFloatingWindow = new Intent(getApplicationContext(),VideoPlayActivity.class);
|
|
||||||
killFloatingWindow.putExtra("killFloat",true);
|
|
||||||
|
|
||||||
startActivity(killFloatingWindow);
|
|
||||||
// TODO: only kill the notification if we no longer have a bound activity
|
|
||||||
stopForeground(true);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
playerNotificationManager.setPlayer(player);
|
|
||||||
|
|
||||||
// external Media control, Android Wear / Google Home etc.
|
|
||||||
MediaSessionCompat mediaSession = new MediaSessionCompat(context, MEDIA_SESSION_TAG);
|
|
||||||
mediaSession.setActive(true);
|
|
||||||
playerNotificationManager.setMediaSessionToken(mediaSession.getSessionToken());
|
|
||||||
MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mediaSession);
|
|
||||||
mediaSessionConnector.setQueueNavigator(new TimelineQueueNavigator(mediaSession) {
|
|
||||||
@Override
|
|
||||||
public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) {
|
|
||||||
return Video.getMediaDescription(context, currentVideo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mediaSessionConnector.setPlayer(player);
|
|
||||||
|
|
||||||
// Audio Focus
|
|
||||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
|
||||||
.setUsage(C.USAGE_MEDIA)
|
|
||||||
.setContentType(C.CONTENT_TYPE_MOVIE)
|
|
||||||
.build();
|
|
||||||
player.setAudioAttributes(audioAttributes, true);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// pause playback on audio output change
|
|
||||||
private class BecomingNoisyReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
|
|
||||||
player.setPlayWhenReady(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package net.schueller.peertube.service
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import net.schueller.peertube.helper.MetaDataHelper.getMetaString
|
||||||
|
import net.schueller.peertube.model.Video.Companion.getMediaDescription
|
||||||
|
import android.os.IBinder
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.media.AudioManager
|
||||||
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||||
|
import android.media.session.PlaybackState
|
||||||
|
import android.content.Intent
|
||||||
|
import android.widget.Toast
|
||||||
|
import net.schueller.peertube.helper.APIUrlHelper
|
||||||
|
import net.schueller.peertube.network.UnsafeOkHttpClient
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource
|
||||||
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||||
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager.MediaDescriptionAdapter
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import net.schueller.peertube.activity.VideoPlayActivity
|
||||||
|
import net.schueller.peertube.activity.VideoListActivity
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||||
|
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Binder
|
||||||
|
import android.util.Log
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.google.android.exoplayer2.*
|
||||||
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
|
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager.NotificationListener
|
||||||
|
import net.schueller.peertube.R.drawable
|
||||||
|
import net.schueller.peertube.R.string
|
||||||
|
import net.schueller.peertube.model.Video
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
class VideoPlayerService : Service() {
|
||||||
|
|
||||||
|
private val mBinder: IBinder = LocalBinder()
|
||||||
|
@JvmField
|
||||||
|
var player: ExoPlayer? = null
|
||||||
|
private var currentVideo: Video? = null
|
||||||
|
private var currentStreamUrl: String? = null
|
||||||
|
private var currentStreamUrlIsHLS = false
|
||||||
|
private var playerNotificationManager: PlayerNotificationManager? = null
|
||||||
|
private val becomeNoisyIntentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||||
|
private val myNoisyAudioStreamReceiver = BecomingNoisyReceiver()
|
||||||
|
override fun onCreate() {
|
||||||
|
Log.v(TAG, "onCreate...")
|
||||||
|
super.onCreate()
|
||||||
|
player = ExoPlayer.Builder(applicationContext)
|
||||||
|
.setTrackSelector(DefaultTrackSelector(applicationContext))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Stop player if audio device changes, e.g. headphones unplugged
|
||||||
|
player!!.addListener(object : Player.Listener {
|
||||||
|
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||||
|
if (playbackState.toLong() == PlaybackState.ACTION_PAUSE) { // this means that pause is available, hence the audio is playing
|
||||||
|
Log.v(TAG, "ACTION_PLAY: $playbackState")
|
||||||
|
registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter)
|
||||||
|
}
|
||||||
|
if (playbackState
|
||||||
|
.toLong() == PlaybackState.ACTION_PLAY
|
||||||
|
) { // this means that play is available, hence the audio is paused or stopped
|
||||||
|
Log.v(TAG, "ACTION_PAUSE: $playbackState")
|
||||||
|
safeUnregisterReceiver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class LocalBinder : Binder() {
|
||||||
|
|
||||||
|
// Return this instance of VideoPlayerService so clients can call public methods
|
||||||
|
val service: VideoPlayerService
|
||||||
|
get() =// Return this instance of VideoPlayerService so clients can call public methods
|
||||||
|
this@VideoPlayerService
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.v(TAG, "onDestroy...")
|
||||||
|
if (playerNotificationManager != null) {
|
||||||
|
playerNotificationManager!!.setPlayer(null)
|
||||||
|
}
|
||||||
|
//Was seeing an error when exiting the program about not unregistering the receiver.
|
||||||
|
safeUnregisterReceiver()
|
||||||
|
if (player != null) {
|
||||||
|
player!!.release()
|
||||||
|
player = null
|
||||||
|
}
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun safeUnregisterReceiver() {
|
||||||
|
try {
|
||||||
|
unregisterReceiver(myNoisyAudioStreamReceiver)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("VideoPlayerService", "attempted to unregister a non-registered service")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
return mBinder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
|
val context: Context = this
|
||||||
|
Log.v(TAG, "onStartCommand...")
|
||||||
|
return if (!URLUtil.isValidUrl(currentStreamUrl)) {
|
||||||
|
Toast.makeText(context, "Invalid URL provided. Unable to play video.", Toast.LENGTH_SHORT).show()
|
||||||
|
START_NOT_STICKY
|
||||||
|
} else {
|
||||||
|
playVideo()
|
||||||
|
START_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentVideo(video: Video?) {
|
||||||
|
Log.v(TAG, "setCurrentVideo...")
|
||||||
|
currentVideo = video
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentStreamUrl(streamUrl: String, isHLS: Boolean) {
|
||||||
|
Log.v(TAG, "setCurrentStreamUrl...$streamUrl")
|
||||||
|
currentStreamUrlIsHLS = isHLS
|
||||||
|
currentStreamUrl = streamUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current playback speed of the player.
|
||||||
|
*
|
||||||
|
* @return the current playback speed of the player.
|
||||||
|
*///Playback speed control
|
||||||
|
var playBackSpeed: Float
|
||||||
|
get() = player!!.playbackParameters.speed
|
||||||
|
set(speed) {
|
||||||
|
Log.v(TAG, "setPlayBackSpeed...")
|
||||||
|
player!!.playbackParameters = PlaybackParameters(speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playVideo() {
|
||||||
|
val context: Context = this
|
||||||
|
|
||||||
|
// We need a valid URL
|
||||||
|
Log.v(TAG, "playVideo...")
|
||||||
|
|
||||||
|
// Produces DataSource instances through which media data is loaded.
|
||||||
|
val okhttpClientBuilder: okhttp3.OkHttpClient.Builder = if (!APIUrlHelper.useInsecureConnection(this)) {
|
||||||
|
okhttp3.OkHttpClient.Builder()
|
||||||
|
} else {
|
||||||
|
UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a data source factory.
|
||||||
|
val dataSourceFactory: OkHttpDataSource.Factory = OkHttpDataSource.Factory(
|
||||||
|
okhttpClientBuilder.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a progressive media source pointing to a stream uri.
|
||||||
|
val mediaSource: MediaSource = if (currentStreamUrlIsHLS) {
|
||||||
|
HlsMediaSource.Factory(dataSourceFactory)
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)))
|
||||||
|
} else {
|
||||||
|
ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the media source to be played.
|
||||||
|
player!!.setMediaSource(mediaSource)
|
||||||
|
|
||||||
|
// Prepare the player.
|
||||||
|
player!!.prepare()
|
||||||
|
|
||||||
|
// Auto play
|
||||||
|
player!!.playWhenReady = true
|
||||||
|
|
||||||
|
//set playback speed to global default
|
||||||
|
val sharedPref = getSharedPreferences(
|
||||||
|
packageName + "_preferences",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
val speed = sharedPref.getString(getString(string.pref_video_speed_key), "1.0")!!.toFloat()
|
||||||
|
playBackSpeed = speed
|
||||||
|
|
||||||
|
playerNotificationManager = PlayerNotificationManager.Builder(
|
||||||
|
this,
|
||||||
|
PLAYBACK_NOTIFICATION_ID,
|
||||||
|
PLAYBACK_CHANNEL_ID,
|
||||||
|
).setMediaDescriptionAdapter(
|
||||||
|
object : MediaDescriptionAdapter {
|
||||||
|
override fun getCurrentContentTitle(player: Player): CharSequence {
|
||||||
|
return currentVideo!!.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCurrentContentIntent(player: Player): PendingIntent? {
|
||||||
|
val intent = Intent(context, VideoPlayActivity::class.java)
|
||||||
|
intent.putExtra(VideoListActivity.EXTRA_VIDEOID, currentVideo!!.uuid)
|
||||||
|
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentContentText(player: Player): CharSequence {
|
||||||
|
return getMetaString(
|
||||||
|
currentVideo!!.createdAt,
|
||||||
|
currentVideo!!.views,
|
||||||
|
baseContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentSubText(player: Player): CharSequence { return ""}
|
||||||
|
|
||||||
|
override fun getCurrentLargeIcon(
|
||||||
|
player: Player,
|
||||||
|
callback: PlayerNotificationManager.BitmapCallback
|
||||||
|
): Bitmap? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).setNotificationListener(
|
||||||
|
object : NotificationListener {
|
||||||
|
|
||||||
|
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
|
||||||
|
super.onNotificationPosted(notificationId, notification, ongoing)
|
||||||
|
if (ongoing) // allow notification to be dismissed if player is stopped
|
||||||
|
startForeground(notificationId, notification)
|
||||||
|
else
|
||||||
|
stopForeground(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
|
||||||
|
super.onNotificationCancelled(notificationId, dismissedByUser)
|
||||||
|
stopSelf()
|
||||||
|
stopForeground(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).setChannelNameResourceId(string.playback_channel_name)
|
||||||
|
.setChannelDescriptionResourceId(string.playback_channel_description)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
playerNotificationManager!!.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
|
||||||
|
playerNotificationManager!!.setSmallIcon(drawable.ic_logo_bw)
|
||||||
|
|
||||||
|
// don't show skip buttons in notification
|
||||||
|
playerNotificationManager!!.setUseNextAction(false)
|
||||||
|
playerNotificationManager!!.setUsePreviousAction(false)
|
||||||
|
|
||||||
|
playerNotificationManager!!.setPlayer(player)
|
||||||
|
|
||||||
|
// external Media control, Android Wear / Google Home etc.
|
||||||
|
val mediaSession = MediaSessionCompat(context, MEDIA_SESSION_TAG)
|
||||||
|
mediaSession.isActive = true
|
||||||
|
playerNotificationManager!!.setMediaSessionToken(mediaSession.sessionToken)
|
||||||
|
val mediaSessionConnector = MediaSessionConnector(mediaSession)
|
||||||
|
mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) {
|
||||||
|
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat {
|
||||||
|
return getMediaDescription(currentVideo!!)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mediaSessionConnector.setPlayer(player)
|
||||||
|
|
||||||
|
// Audio Focus
|
||||||
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
|
.setUsage(C.USAGE_MEDIA)
|
||||||
|
.setContentType(C.CONTENT_TYPE_MOVIE)
|
||||||
|
.build()
|
||||||
|
player!!.setAudioAttributes(audioAttributes, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pause playback on audio output change
|
||||||
|
private inner class BecomingNoisyReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {
|
||||||
|
player!!.playWhenReady = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val TAG = "VideoPlayerService"
|
||||||
|
private const val MEDIA_SESSION_TAG = "peertube_player"
|
||||||
|
private const val PLAYBACK_CHANNEL_ID = "playback_channel"
|
||||||
|
private const val PLAYBACK_NOTIFICATION_ID = 1
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/res/drawable/ic_chevron_down.xml
Normal file
13
app/src/main/res/drawable/ic_chevron_down.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M6,9l6,6l6,-6"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
20
app/src/main/res/drawable/ic_close.xml
Normal file
20
app/src/main/res/drawable/ic_close.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M18,6L6,18"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,6L18,18"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
27
app/src/main/res/drawable/ic_download.xml
Normal file
27
app/src/main/res/drawable/ic_download.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M21,15v4a2,2 0,0 1,-2 2H5a2,2 0,0 1,-2 -2v-4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M7,10l5,5l5,-5"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12,15L12,3"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/ic_fast_forward.xml
Normal file
11
app/src/main/res/drawable/ic_fast_forward.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<vector android:height="52dp" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M13,19l9,-7l-9,-7l0,14z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M2,19l9,-7l-9,-7l0,14z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
</vector>
|
20
app/src/main/res/drawable/ic_flag.xml
Normal file
20
app/src/main/res/drawable/ic_flag.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M4,15s1,-1 4,-1 5,2 8,2 4,-1 4,-1V3s-1,1 -4,1 -5,-2 -8,-2 -4,1 -4,1z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4,22L4,15"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_pause.xml
Normal file
10
app/src/main/res/drawable/ic_pause.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector android:height="52dp" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M6,4h4v16h-4z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M14,4h4v16h-4z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
</vector>
|
7
app/src/main/res/drawable/ic_play.xml
Normal file
7
app/src/main/res/drawable/ic_play.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="52dp" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M5,3l14,9l-14,9l0,-18z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_playlist_add.xml
Normal file
5
app/src/main/res/drawable/ic_playlist_add.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="426.7"
|
||||||
|
android:viewportWidth="426.7" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M0,64h256v42.7H0zM0,149.3h256V192H0zM0,234.7h170.7v42.7H0z"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M341.3,234.7v-85.4h-42.6v85.4h-85.4v42.6h85.4v85.4h42.6v-85.4h85.4v-42.6z"/>
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/ic_rewind.xml
Normal file
11
app/src/main/res/drawable/ic_rewind.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<vector android:height="52dp" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="52dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M11,19l-9,-7l9,-7l0,14z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M22,19l-9,-7l9,-7l0,14z"
|
||||||
|
android:strokeColor="#ffffff" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
</vector>
|
41
app/src/main/res/drawable/ic_share_2.xml
Normal file
41
app/src/main/res/drawable/ic_share_2.xml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M18,5m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M18,19m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M8.59,13.51L15.42,17.49"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M15.41,6.51L8.59,10.49"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
20
app/src/main/res/drawable/ic_slash.xml
Normal file
20
app/src/main/res/drawable/ic_slash.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4.93,4.93L19.07,19.07"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
13
app/src/main/res/drawable/ic_thumbs_down.xml
Normal file
13
app/src/main/res/drawable/ic_thumbs_down.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M10,15v4a3,3 0,0 0,3 3l4,-9L17,2L5.72,2a2,2 0,0 0,-2 1.7l-1.38,9a2,2 0,0 0,2 2.3zM17,2h2.67A2.31,2.31 0,0 1,22 4v7a2.31,2.31 0,0 1,-2.33 2L17,13"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
13
app/src/main/res/drawable/ic_thumbs_down_filled.xml
Normal file
13
app/src/main/res/drawable/ic_thumbs_down_filled.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M10,15v4a3,3 0,0 0,3 3l4,-9L17,2L5.72,2a2,2 0,0 0,-2 1.7l-1.38,9a2,2 0,0 0,2 2.3zM17,2h2.67A2.31,2.31 0,0 1,22 4v7a2.31,2.31 0,0 1,-2.33 2L17,13"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#555555"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
13
app/src/main/res/drawable/ic_thumbs_up.xml
Normal file
13
app/src/main/res/drawable/ic_thumbs_up.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M14,9V5a3,3 0,0 0,-3 -3l-4,9v11h11.28a2,2 0,0 0,2 -1.7l1.38,-9a2,2 0,0 0,-2 -2.3zM7,22H4a2,2 0,0 1,-2 -2v-7a2,2 0,0 1,2 -2h3"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
13
app/src/main/res/drawable/ic_thumbs_up_filled.xml
Normal file
13
app/src/main/res/drawable/ic_thumbs_up_filled.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M14,9V5a3,3 0,0 0,-3 -3l-4,9v11h11.28a2,2 0,0 0,2 -1.7l1.38,-9a2,2 0,0 0,-2 -2.3zM7,22H4a2,2 0,0 1,-2 -2v-7a2,2 0,0 1,2 -2h3"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#555555"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
@ -6,36 +6,31 @@
|
|||||||
android:keepScreenOn="true"
|
android:keepScreenOn="true"
|
||||||
tools:context="net.schueller.peertube.activity.VideoPlayActivity">
|
tools:context="net.schueller.peertube.activity.VideoPlayActivity">
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout
|
||||||
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<fragment android:name="net.schueller.peertube.fragment.VideoPlayerFragment"
|
<fragment
|
||||||
android:id="@+id/video_player_fragment"
|
android:id="@+id/video_player_fragment"
|
||||||
|
android:name="net.schueller.peertube.fragment.VideoPlayerFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="250dp" />
|
android:layout_height="250dp" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_below="@id/video_player_fragment"
|
||||||
|
android:layout_marginTop="250dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<RelativeLayout
|
<fragment
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/video_meta_data_fragment"
|
||||||
android:layout_height="match_parent"
|
android:name="net.schueller.peertube.fragment.VideoMetaDataFragment"
|
||||||
android:layout_below="@id/video_player_fragment"
|
android:layout_width="match_parent"
|
||||||
android:orientation="vertical">
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
<ScrollView
|
|
||||||
android:id="@+id/login_form"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<fragment android:name="net.schueller.peertube.fragment.VideoMetaDataFragment"
|
|
||||||
android:id="@+id/video_meta_data_fragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
225
app/src/main/res/layout/fragment_video_description.xml
Normal file
225
app/src/main/res/layout/fragment_video_description.xml
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout android:layout_width="match_parent"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:background="?android:colorBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text="@string/video_meta_title_description"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_description_close_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="end"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
|
||||||
|
android:id="@+id/description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginStart="18dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:autoLink="web"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/description"
|
||||||
|
android:layout_marginStart="18dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_privacy"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_privacy"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_category"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_category"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_license"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_license"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||||
|
tools:text="@tools:sample/lorem" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_language"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_language"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_tags"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_tags"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
@ -1,364 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/videoMetaFragment"
|
||||||
android:padding="6dp">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView
|
<!-- Related Videos -->
|
||||||
android:id="@+id/avatar"
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:layout_width="72dp"
|
android:id="@+id/relatedVideosView"
|
||||||
android:layout_height="72dp"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_marginTop="0dp"
|
|
||||||
android:contentDescription="@string/video_row_account_avatar"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingEnd="12dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/sl_row_name"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent">
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_marginStart="6dp"
|
|
||||||
android:layout_marginTop="0dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:layout_toEndOf="@+id/avatar"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
|
||||||
|
|
||||||
<TextView
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
android:id="@+id/videoMeta"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/sl_row_name"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_marginStart="6dp"
|
|
||||||
android:layout_marginTop="0dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:layout_toEndOf="@+id/avatar"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/videoOwner"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/videoMeta"
|
|
||||||
android:layout_marginStart="6dp"
|
|
||||||
android:layout_marginTop="0dp"
|
|
||||||
android:layout_marginEnd="0dp"
|
|
||||||
android:layout_toEndOf="@id/avatar"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/moreButton"
|
|
||||||
android:layout_width="45dp"
|
|
||||||
android:layout_height="45dp"
|
|
||||||
android:layout_marginStart="-16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="0dp"
|
|
||||||
android:layout_toEndOf="@+id/sl_row_name"
|
|
||||||
android:background="@null"
|
|
||||||
android:contentDescription="@string/descr_overflow_button"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/video_actions"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/videoOwner"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="65dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/video_thumbs_up"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_thumbs_up_total"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="65dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/video_thumbs_down"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_thumbs_down_total"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="65dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/video_share"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_share_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_share"
|
|
||||||
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="65dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/video_download"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_download_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_download"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/video_actions"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:autoLink="web"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/description"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_privacy"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="1dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_privacy"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
|
||||||
tools:text="@tools:sample/lorem/random"
|
|
||||||
android:lines="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_category"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="1dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_category"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
|
||||||
tools:text="@tools:sample/lorem/random"
|
|
||||||
android:lines="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_license"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="1dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_license"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
|
||||||
tools:text="@tools:sample/lorem"
|
|
||||||
android:lines="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_language"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="1dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_language"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption"
|
|
||||||
tools:text="@tools:sample/lorem/random"
|
|
||||||
android:lines="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/video_meta_button_tags"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="1dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_tags"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center|start"
|
|
||||||
tools:text="@tools:sample/lorem/random"
|
|
||||||
android:lines="2"
|
|
||||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
94
app/src/main/res/layout/item_video_comments_overview.xml
Normal file
94
app/src/main/res/layout/item_video_comments_overview.xml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:id="@+id/video_title_block"
|
||||||
|
>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/video_comments_title_wrapper"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_comments_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/video_comments_title"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_comments_total_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_toEndOf="@+id/video_comments_title"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:clickable="false"
|
||||||
|
android:contentDescription="@string/video_meta_show_description"
|
||||||
|
android:src="@drawable/ic_chevron_down"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/video_highlighted_comment_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/video_comments_title_wrapper">
|
||||||
|
|
||||||
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/video_highlighted_avatar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_highlighted_comment"
|
||||||
|
android:layout_toEndOf="@+id/video_highlighted_avatar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
402
app/src/main/res/layout/item_video_meta.xml
Normal file
402
app/src/main/res/layout/item_video_meta.xml
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent">
|
||||||
|
|
||||||
|
<!-- Video Title Block -->
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/video_title_block"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:paddingTop="6dp">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/video_open_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginEnd="6dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:clickable="false"
|
||||||
|
android:contentDescription="@string/video_meta_show_description"
|
||||||
|
android:src="@drawable/ic_chevron_down"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/videoMeta"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/video_open_description"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<!-- video actions -->
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/video_actions_block"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/video_title_block"
|
||||||
|
android:paddingBottom="6dp"
|
||||||
|
android:scrollbars="none">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/video_actions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="18dp"
|
||||||
|
android:paddingEnd="18dp"
|
||||||
|
android:paddingStart="18dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_thumbs_up_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_thumbs_up"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_thumbs_up"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_thumbs_up_total"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_thumbs_down_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_thumbs_down"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_thumbs_down"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_thumbs_down_total"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_share_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_share"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_share_2"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_share_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_share"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_download_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_download"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_download"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_download_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_meta_button_download"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_add_to_playlist_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_add_to_playlist"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_playlist_add"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_add_to_playlist_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_add_to_playlist"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_block_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_block"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_slash"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/vvideo_block_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_block"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:id="@+id/video_flag_wrapper"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/video_flag"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:src="@drawable/ic_flag"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/vvideo_flag_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/video_flag"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_below="@+id/video_actions_block"
|
||||||
|
android:id="@+id/video_action_block_line"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:background="?android:colorEdgeEffect"
|
||||||
|
android:height="1dp"
|
||||||
|
android:gravity="center_horizontal"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/video_account_block"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/video_action_block_line"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingEnd="6dp">
|
||||||
|
|
||||||
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="72dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/video_row_account_avatar"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="12dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_toEndOf="@+id/avatar"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/videoOwner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/videoOwnerSubscribers"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/videoOwnerSubscribeButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:text=""
|
||||||
|
android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <TextView-->
|
||||||
|
<!-- android:id="@+id/moreButton"-->
|
||||||
|
<!-- android:layout_width="45dp"-->
|
||||||
|
<!-- android:layout_height="45dp"-->
|
||||||
|
<!-- android:layout_marginStart="-16dp"-->
|
||||||
|
<!-- android:layout_marginTop="16dp"-->
|
||||||
|
<!-- android:layout_marginEnd="0dp"-->
|
||||||
|
<!-- android:layout_toEndOf="@+id/sl_row_name"-->
|
||||||
|
<!-- android:background="@null"-->
|
||||||
|
<!-- android:contentDescription="@string/descr_overflow_button"-->
|
||||||
|
<!-- android:textAppearance="@style/Base.TextAppearance.AppCompat.Caption" />-->
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_below="@+id/video_account_block"
|
||||||
|
android:id="@+id/video_account_block_line"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:background="?android:colorEdgeEffect"
|
||||||
|
android:height="1dp"
|
||||||
|
android:gravity="center_horizontal"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -8,7 +8,7 @@
|
|||||||
android:background="#CC000000"
|
android:background="#CC000000"
|
||||||
android:layoutDirection="ltr"
|
android:layoutDirection="ltr"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:targetApi="28">
|
tools:targetApi="32">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/exo_more_button"
|
android:id="@+id/exo_more_button"
|
||||||
@ -18,14 +18,14 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/exo_more"
|
android:id="@+id/exo_more"
|
||||||
android:layout_width="18dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:paddingTop="12dp"
|
android:paddingTop="12dp"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:textColor="#FFBEBEBE"
|
android:textColor="#FFBEBEBE"
|
||||||
android:textSize="12sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
@ -43,27 +43,63 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingTop="8dp">
|
android:paddingTop="8dp">
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@id/exo_rew"
|
android:id="@id/exo_rew"
|
||||||
style="@style/ExoMediaButton.Rewind" />
|
android:layout_width="72sp"
|
||||||
|
android:layout_height="52sp"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/exo_controls_rewind_description"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_rewind" />
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@id/exo_repeat_toggle"
|
android:id="@id/exo_repeat_toggle"
|
||||||
style="@style/ExoMediaButton" />
|
style="@style/ExoMediaButton" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@id/exo_play"
|
android:id="@id/exo_play"
|
||||||
style="@style/ExoMediaButton.Play" />
|
android:contentDescription="@string/exo_controls_play_description"
|
||||||
|
android:src="@drawable/ic_play"
|
||||||
|
android:layout_height="52sp"
|
||||||
|
android:layout_width="72sp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@id/exo_pause"
|
android:id="@id/exo_pause"
|
||||||
style="@style/ExoMediaButton.Pause" />
|
android:contentDescription="@string/exo_controls_pause_description"
|
||||||
|
android:src="@drawable/ic_pause"
|
||||||
|
android:layout_height="52sp"
|
||||||
|
android:layout_width="72sp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@id/exo_ffwd"
|
android:id="@id/exo_ffwd"
|
||||||
style="@style/ExoMediaButton.FastForward" />
|
android:layout_width="72sp"
|
||||||
|
android:layout_height="52sp"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/exo_controls_fastforward_description"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_fast_forward" />
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
@ -86,9 +122,29 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:paddingLeft="12dp"
|
android:paddingStart="12dp"
|
||||||
android:paddingRight="12dp"
|
android:paddingEnd="2dp"
|
||||||
android:textColor="#FFBEBEBE"
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:textColor="#BABABA"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/player_time_seperator" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@id/exo_duration"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="6dp"
|
||||||
|
android:textColor="#BABABA"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@ -96,18 +152,6 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@id/exo_duration"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:paddingLeft="6dp"
|
|
||||||
android:paddingRight="6dp"
|
|
||||||
android:textColor="#FFBEBEBE"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/exo_fullscreen_button"
|
android:id="@+id/exo_fullscreen_button"
|
||||||
@ -117,13 +161,13 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/exo_fullscreen"
|
android:id="@+id/exo_fullscreen"
|
||||||
android:layout_width="18dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:textColor="#FFBEBEBE"
|
android:textColor="#FFBEBEBE"
|
||||||
android:textSize="12sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
@ -146,20 +190,20 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<!-- <LinearLayout-->
|
||||||
android:visibility="gone"
|
<!-- android:visibility="gone"-->
|
||||||
android:id="@+id/exo_torrent_status"
|
<!-- android:id="@+id/exo_torrent_status"-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_height="wrap_content">
|
<!-- android:layout_height="wrap_content">-->
|
||||||
|
|
||||||
<ProgressBar
|
<!-- <ProgressBar-->
|
||||||
android:id="@+id/torrent_progress"
|
<!-- android:id="@+id/torrent_progress"-->
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
<!-- style="?android:attr/progressBarStyleHorizontal"-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_height="wrap_content"
|
<!-- android:layout_height="wrap_content"-->
|
||||||
android:indeterminate="false"
|
<!-- android:indeterminate="false"-->
|
||||||
android:max="100" />
|
<!-- android:max="100" />-->
|
||||||
|
|
||||||
</LinearLayout>
|
<!-- </LinearLayout>-->
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -64,6 +64,7 @@
|
|||||||
<string name="title_activity_video_play" translatable="false">VideoPlayActivity</string>
|
<string name="title_activity_video_play" translatable="false">VideoPlayActivity</string>
|
||||||
|
|
||||||
<string name="playback_channel_name" translatable="false">PeerTube</string>
|
<string name="playback_channel_name" translatable="false">PeerTube</string>
|
||||||
|
<string name="playback_channel_description" translatable="false">playback_channel</string>
|
||||||
|
|
||||||
<string name="peertube_instance_search_default_description" translatable="false">PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.</string>
|
<string name="peertube_instance_search_default_description" translatable="false">PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.</string>
|
||||||
|
|
||||||
|
@ -366,4 +366,14 @@
|
|||||||
<string name="video_list_live_marker">LIVE</string>
|
<string name="video_list_live_marker">LIVE</string>
|
||||||
<string name="video_get_full_description_failed">Getting full video description failed</string>
|
<string name="video_get_full_description_failed">Getting full video description failed</string>
|
||||||
<string name="video_description_read_more">Read More</string>
|
<string name="video_description_read_more">Read More</string>
|
||||||
|
<string name="player_time_seperator">/</string>
|
||||||
|
<string name="video_meta_show_description">Show Description</string>
|
||||||
|
<string name="video_meta_title_description">Description</string>
|
||||||
|
<string name="video_add_to_playlist">Save</string>
|
||||||
|
<string name="video_block">Block</string>
|
||||||
|
<string name="video_flag">Flag</string>
|
||||||
|
<string name="video_feature_not_yet_implemented">This feature has not yet been implemented. Coming soon!</string>
|
||||||
|
<string name="subscribe">Subscribe</string>
|
||||||
|
<string name="unsubscribe">Unsubscribe</string>
|
||||||
|
<string name="video_comments_title">Comments</string>
|
||||||
</resources>
|
</resources>
|
@ -76,12 +76,12 @@
|
|||||||
app:title="@string/pref_background_behavior"
|
app:title="@string/pref_background_behavior"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
<SwitchPreference
|
<!-- <SwitchPreference-->
|
||||||
app:defaultValue="false"
|
<!-- app:defaultValue="false"-->
|
||||||
app:key="@string/pref_torrent_player_key"
|
<!-- app:key="@string/pref_torrent_player_key"-->
|
||||||
app:summary="@string/pref_description_torrent_player"
|
<!-- app:summary="@string/pref_description_torrent_player"-->
|
||||||
app:title="@string/pref_title_torrent_player"
|
<!-- app:title="@string/pref_title_torrent_player"-->
|
||||||
app:iconSpaceReserved="false"/>
|
<!-- app:iconSpaceReserved="false"/>-->
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
|
||||||
ext.kotlin_version = '1.5.31'
|
ext.kotlin_version = '1.6.10'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||||
@ -21,7 +21,6 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
maven {
|
||||||
url 'https://oss.sonatype.org/content/repositories/snapshots'
|
url 'https://oss.sonatype.org/content/repositories/snapshots'
|
||||||
|
Loading…
Reference in New Issue
Block a user