mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-04 07:09:53 +00:00
Media3 migration.
This commit is contained in:
parent
fa12f8277c
commit
3b8d256bad
15 changed files with 213 additions and 239 deletions
|
@ -172,13 +172,14 @@ dependencies {
|
||||||
implementation("com.caoccao.javet:javet-android:2.2.1")
|
implementation("com.caoccao.javet:javet-android:2.2.1")
|
||||||
|
|
||||||
//Exoplayer
|
//Exoplayer
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1'
|
implementation 'androidx.media3:media3-exoplayer:1.2.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1'
|
implementation 'androidx.media3:media3-exoplayer-dash:1.2.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
|
implementation 'androidx.media3:media3-ui:1.2.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1'
|
implementation 'androidx.media3:media3-session:1.2.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.19.1'
|
implementation 'androidx.media3:media3-exoplayer-hls:1.2.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1'
|
implementation 'androidx.media3:media3-exoplayer-rtsp:1.2.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-transformer:2.19.1'
|
implementation 'androidx.media3:media3-exoplayer-smoothstreaming:1.2.0'
|
||||||
|
implementation 'androidx.media3:media3-transformer:1.2.0'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||||
|
|
||||||
|
import androidx.media3.datasource.DefaultHttpDataSource
|
||||||
|
import androidx.media3.datasource.HttpDataSource
|
||||||
import com.caoccao.javet.values.V8Value
|
import com.caoccao.javet.values.V8Value
|
||||||
import com.caoccao.javet.values.reference.V8ValueObject
|
import com.caoccao.javet.values.reference.V8ValueObject
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
|
@ -11,8 +11,6 @@ import com.futo.platformplayer.engine.IV8PluginConfig
|
||||||
import com.futo.platformplayer.engine.V8Plugin
|
import com.futo.platformplayer.engine.V8Plugin
|
||||||
import com.futo.platformplayer.orNull
|
import com.futo.platformplayer.orNull
|
||||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource
|
|
||||||
|
|
||||||
abstract class JSSource {
|
abstract class JSSource {
|
||||||
protected val _config: IV8PluginConfig;
|
protected val _config: IV8PluginConfig;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.fragment.mainactivity.main
|
package com.futo.platformplayer.fragment.mainactivity.main
|
||||||
|
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
|
@ -14,7 +12,6 @@ import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -32,6 +29,12 @@ import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.Format
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.datasource.HttpDataSource
|
||||||
|
import androidx.media3.ui.PlayerControlView
|
||||||
|
import androidx.media3.ui.TimeBar
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
@ -138,11 +141,6 @@ import com.futo.polycentric.core.ContentType
|
||||||
import com.futo.polycentric.core.Models
|
import com.futo.polycentric.core.Models
|
||||||
import com.futo.polycentric.core.Opinion
|
import com.futo.polycentric.core.Opinion
|
||||||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||||
import com.google.android.exoplayer2.C
|
|
||||||
import com.google.android.exoplayer2.Format
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerControlView
|
|
||||||
import com.google.android.exoplayer2.ui.TimeBar
|
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -155,7 +153,6 @@ import java.time.OffsetDateTime
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
|
||||||
class VideoDetailView : ConstraintLayout {
|
class VideoDetailView : ConstraintLayout {
|
||||||
private val TAG = "VideoDetailView"
|
private val TAG = "VideoDetailView"
|
||||||
|
|
||||||
|
@ -303,7 +300,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
Pair(0, 10) //around live, try every 10 seconds
|
Pair(0, 10) //around live, try every 10 seconds
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@androidx.annotation.OptIn(UnstableApi::class)
|
||||||
constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) {
|
constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) {
|
||||||
inflate(context, R.layout.fragview_video_detail, this);
|
inflate(context, R.layout.fragview_video_detail, this);
|
||||||
|
|
||||||
|
@ -312,7 +309,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
_cast = findViewById(R.id.videodetail_cast);
|
_cast = findViewById(R.id.videodetail_cast);
|
||||||
_player = findViewById(R.id.videodetail_player);
|
_player = findViewById(R.id.videodetail_player);
|
||||||
_playerProgress = findViewById(R.id.videodetail_progress);
|
_playerProgress = findViewById(R.id.videodetail_progress);
|
||||||
_timeBar = _playerProgress.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
|
_timeBar = _playerProgress.findViewById(androidx.media3.ui.R.id.exo_progress);
|
||||||
_title = findViewById(R.id.videodetail_title);
|
_title = findViewById(R.id.videodetail_title);
|
||||||
_subTitle = findViewById(R.id.videodetail_meta);
|
_subTitle = findViewById(R.id.videodetail_meta);
|
||||||
_platform = findViewById(R.id.videodetail_platform);
|
_platform = findViewById(R.id.videodetail_platform);
|
||||||
|
@ -574,8 +571,8 @@ class VideoDetailView : ConstraintLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
_playerProgress.player = _player.exoPlayer?.player;
|
_playerProgress.player = _player.exoPlayer?.player;
|
||||||
_playerProgress.setProgressUpdateListener { position, _ ->
|
_playerProgress.setProgressUpdateListener { _, _ ->
|
||||||
StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, position);
|
StatePlayer.instance.updateMediaSessionPlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
StatePlayer.instance.onQueueChanged.subscribe(this) {
|
StatePlayer.instance.onQueueChanged.subscribe(this) {
|
||||||
|
@ -1497,6 +1494,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
|
@androidx.annotation.OptIn(UnstableApi::class)
|
||||||
private fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean){
|
private fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean){
|
||||||
Logger.i(TAG, "onSourceChanged(videoSource=$videoSource, audioSource=$audioSource, resume=$resume)")
|
Logger.i(TAG, "onSourceChanged(videoSource=$videoSource, audioSource=$audioSource, resume=$resume)")
|
||||||
|
|
||||||
|
@ -1532,7 +1530,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
private var _didTriggerDatasourceError = false;
|
private var _didTriggerDatasourceError = false;
|
||||||
private fun onDataSourceError(exception: Throwable) {
|
private fun onDataSourceError(exception: Throwable) {
|
||||||
Logger.e(TAG, "onDataSourceError", exception);
|
Logger.e(TAG, "onDataSourceError", exception);
|
||||||
if(exception.cause != null && exception.cause is InvalidResponseCodeException && (exception.cause!! as InvalidResponseCodeException).responseCode == 403) {
|
if(exception.cause != null && exception.cause is HttpDataSource.InvalidResponseCodeException && (exception.cause!! as HttpDataSource.InvalidResponseCodeException).responseCode == 403) {
|
||||||
val currentVideo = video
|
val currentVideo = video
|
||||||
if(currentVideo == null || currentVideo !is IPluginSourced)
|
if(currentVideo == null || currentVideo !is IPluginSourced)
|
||||||
return;
|
return;
|
||||||
|
@ -1611,6 +1609,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
val v = video ?: return;
|
val v = video ?: return;
|
||||||
updateQualitySourcesOverlay(v, videoLocal, liveStreamVideoFormats, liveStreamAudioFormats);
|
updateQualitySourcesOverlay(v, videoLocal, liveStreamVideoFormats, liveStreamAudioFormats);
|
||||||
}
|
}
|
||||||
|
@androidx.annotation.OptIn(UnstableApi::class)
|
||||||
private fun updateQualitySourcesOverlay(videoDetails: IPlatformVideoDetails?, videoLocal: VideoLocal? = null, liveStreamVideoFormats: List<Format>? = null, liveStreamAudioFormats: List<Format>? = null) {
|
private fun updateQualitySourcesOverlay(videoDetails: IPlatformVideoDetails?, videoLocal: VideoLocal? = null, liveStreamVideoFormats: List<Format>? = null, liveStreamAudioFormats: List<Format>? = null) {
|
||||||
Logger.i(TAG, "updateQualitySourcesOverlay");
|
Logger.i(TAG, "updateQualitySourcesOverlay");
|
||||||
|
|
||||||
|
@ -1730,7 +1729,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
{ handleSelectAudioTrack(it) });
|
{ handleSelectAudioTrack(it) });
|
||||||
}.toList().toTypedArray())
|
}.toList().toTypedArray())
|
||||||
else null,
|
else null,
|
||||||
if(video?.subtitles?.isNotEmpty() ?: false && video != null)
|
if(video?.subtitles?.isNotEmpty() == true)
|
||||||
SlideUpMenuGroup(this.context, context.getString(R.string.subtitles), "subtitles",
|
SlideUpMenuGroup(this.context, context.getString(R.string.subtitles), "subtitles",
|
||||||
*video.subtitles
|
*video.subtitles
|
||||||
.map {
|
.map {
|
||||||
|
@ -1792,7 +1791,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
_cast.setIsPlaying(playing);
|
_cast.setIsPlaying(playing);
|
||||||
} else {
|
} else {
|
||||||
StatePlayer.instance.updateMediaSession( null);
|
StatePlayer.instance.updateMediaSession( null);
|
||||||
StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, _player.exoPlayer?.player?.currentPosition ?: 0);
|
StatePlayer.instance.updateMediaSessionPlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(playing) {
|
if(playing) {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.helpers
|
package com.futo.platformplayer.helpers
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
||||||
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
|
@ -15,11 +14,12 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import com.google.android.exoplayer2.source.MediaSource
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.google.android.exoplayer2.source.dash.DashMediaSource
|
import androidx.media3.datasource.ResolvingDataSource
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
|
import androidx.media3.exoplayer.dash.DashMediaSource
|
||||||
import com.google.android.exoplayer2.upstream.ResolvingDataSource
|
import androidx.media3.exoplayer.dash.manifest.DashManifestParser
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class VideoHelper {
|
class VideoHelper {
|
||||||
|
@ -121,7 +121,7 @@ class VideoHelper {
|
||||||
return bestSource;
|
return bestSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@OptIn(UnstableApi::class)
|
||||||
fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource {
|
fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource {
|
||||||
val urlToUse = videoSource.getVideoUrl();
|
val urlToUse = videoSource.getVideoUrl();
|
||||||
val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse,
|
val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse,
|
||||||
|
@ -140,14 +140,13 @@ class VideoHelper {
|
||||||
);
|
);
|
||||||
|
|
||||||
val manifest = DashManifestParser().parse(Uri.parse(""), manifestConfig.byteInputStream());
|
val manifest = DashManifestParser().parse(Uri.parse(""), manifestConfig.byteInputStream());
|
||||||
|
|
||||||
return DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec ->
|
return DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec ->
|
||||||
Logger.v("PLAYBACK", "Video REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null);
|
Logger.v("PLAYBACK", "Video REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null);
|
||||||
return@Resolver dataSpec;
|
return@Resolver dataSpec;
|
||||||
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build())
|
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@OptIn(UnstableApi::class)
|
||||||
fun convertItagSourceToChunkedDashSource(audioSource: JSAudioUrlRangeSource) : MediaSource {
|
fun convertItagSourceToChunkedDashSource(audioSource: JSAudioUrlRangeSource) : MediaSource {
|
||||||
val manifestConfig = ProgressiveDashManifestCreator.fromAudioProgressiveStreamingUrl(audioSource.getAudioUrl(),
|
val manifestConfig = ProgressiveDashManifestCreator.fromAudioProgressiveStreamingUrl(audioSource.getAudioUrl(),
|
||||||
audioSource.duration?.times(1000) ?: 0,
|
audioSource.duration?.times(1000) ?: 0,
|
||||||
|
|
|
@ -17,7 +17,6 @@ class InstallReceiver : BroadcastReceiver() {
|
||||||
val onReceiveResult = Event1<String?>();
|
val onReceiveResult = Event1<String?>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1);
|
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1);
|
||||||
Logger.i(TAG, "Received status $status.");
|
Logger.i(TAG, "Received status $status.");
|
||||||
|
|
|
@ -13,15 +13,14 @@ import android.graphics.drawable.Drawable
|
||||||
import android.media.AudioFocusRequest
|
import android.media.AudioFocusRequest
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioManager.OnAudioFocusChangeListener
|
import android.media.AudioManager.OnAudioFocusChangeListener
|
||||||
import android.media.MediaMetadata
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.SystemClock
|
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.session.MediaSession
|
||||||
|
import androidx.media3.session.MediaStyleNotificationHelper
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
@ -51,7 +50,7 @@ class MediaPlaybackService : Service() {
|
||||||
private var _audioManager: AudioManager? = null;
|
private var _audioManager: AudioManager? = null;
|
||||||
private var _notificationManager: NotificationManager? = null;
|
private var _notificationManager: NotificationManager? = null;
|
||||||
private var _notificationChannel: NotificationChannel? = null;
|
private var _notificationChannel: NotificationChannel? = null;
|
||||||
private var _mediaSession: MediaSessionCompat? = null;
|
private var _mediaSession: MediaSession? = null;
|
||||||
private var _hasFocus: Boolean = false;
|
private var _hasFocus: Boolean = false;
|
||||||
private var _focusRequest: AudioFocusRequest? = null;
|
private var _focusRequest: AudioFocusRequest? = null;
|
||||||
private var _audioFocusLossTime_ms: Long? = null
|
private var _audioFocusLossTime_ms: Long? = null
|
||||||
|
@ -82,6 +81,7 @@ class MediaPlaybackService : Service() {
|
||||||
|
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun setupNotificationRequirements() {
|
fun setupNotificationRequirements() {
|
||||||
_audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager;
|
_audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager;
|
||||||
_notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
|
_notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
|
||||||
|
@ -91,12 +91,15 @@ class MediaPlaybackService : Service() {
|
||||||
};
|
};
|
||||||
_notificationManager!!.createNotificationChannel(_notificationChannel!!);
|
_notificationManager!!.createNotificationChannel(_notificationChannel!!);
|
||||||
|
|
||||||
_mediaSession = MediaSessionCompat(this, "PlayerState");
|
_mediaSession = MediaSession.Builder(this, StatePlayer.instance.getPlayerOrCreate(this).player)
|
||||||
_mediaSession?.setPlaybackState(PlaybackStateCompat.Builder()
|
|
||||||
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1f)
|
.setCallback(object: MediaSession.Callback {
|
||||||
.build());
|
override fun onMediaButtonEvent(session: MediaSession, controllerInfo: MediaSession.ControllerInfo, intent: Intent): Boolean {
|
||||||
_mediaSession?.setCallback(object: MediaSessionCompat.Callback() {
|
//TODO: Reimplement
|
||||||
override fun onSeekTo(pos: Long) {
|
return super.onMediaButtonEvent(session, controllerInfo, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*override fun onSeekTo(pos: Long) {
|
||||||
super.onSeekTo(pos)
|
super.onSeekTo(pos)
|
||||||
Logger.i(TAG, "Media session callback onSeekTo(pos = $pos)");
|
Logger.i(TAG, "Media session callback onSeekTo(pos = $pos)");
|
||||||
MediaControlReceiver.onSeekToReceived.emit(pos);
|
MediaControlReceiver.onSeekToReceived.emit(pos);
|
||||||
|
@ -130,8 +133,12 @@ class MediaPlaybackService : Service() {
|
||||||
super.onSkipToNext()
|
super.onSkipToNext()
|
||||||
Logger.i(TAG, "Media session callback onSkipToNext()");
|
Logger.i(TAG, "Media session callback onSkipToNext()");
|
||||||
MediaControlReceiver.onNextReceived.emit();
|
MediaControlReceiver.onNextReceived.emit();
|
||||||
}
|
}*/
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
/*_mediaSession?.setPlaybackState(PlaybackStateCompat.Builder()
|
||||||
|
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1f)
|
||||||
|
.build());*/
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
@ -186,15 +193,7 @@ class MediaPlaybackService : Service() {
|
||||||
if(_notificationChannel == null || _mediaSession == null)
|
if(_notificationChannel == null || _mediaSession == null)
|
||||||
setupNotificationRequirements();
|
setupNotificationRequirements();
|
||||||
|
|
||||||
_mediaSession?.setMetadata(
|
|
||||||
MediaMetadataCompat.Builder()
|
|
||||||
.putString(MediaMetadata.METADATA_KEY_ARTIST, video.author.name)
|
|
||||||
.putString(MediaMetadata.METADATA_KEY_TITLE, video.name)
|
|
||||||
.putLong(MediaMetadata.METADATA_KEY_DURATION, video.duration * 1000)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
val thumbnail = video.thumbnails.getHQThumbnail();
|
val thumbnail = video.thumbnails.getHQThumbnail();
|
||||||
|
|
||||||
_notif_last_video = video;
|
_notif_last_video = video;
|
||||||
|
|
||||||
if(isUpdating)
|
if(isUpdating)
|
||||||
|
@ -221,6 +220,7 @@ class MediaPlaybackService : Service() {
|
||||||
private fun generateMediaAction(icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action {
|
private fun generateMediaAction(icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action {
|
||||||
return NotificationCompat.Action.Builder(icon, title, intent).build();
|
return NotificationCompat.Action.Builder(icon, title, intent).build();
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun notifyMediaSession(video: IPlatformVideo?, desiredBitmap: Bitmap?) {
|
private fun notifyMediaSession(video: IPlatformVideo?, desiredBitmap: Bitmap?) {
|
||||||
val channel = _notificationChannel ?: return;
|
val channel = _notificationChannel ?: return;
|
||||||
val session = _mediaSession ?: return;
|
val session = _mediaSession ?: return;
|
||||||
|
@ -249,14 +249,9 @@ class MediaPlaybackService : Service() {
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE))
|
.setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE))
|
||||||
.setStyle(if(hasQueue)
|
.setStyle(
|
||||||
androidx.media.app.NotificationCompat.MediaStyle()
|
if(hasQueue) MediaStyleNotificationHelper.MediaStyle(session)//.setShowActionsInCompactView(0, 1, 2)
|
||||||
.setMediaSession(session.sessionToken)
|
else MediaStyleNotificationHelper.MediaStyle(session))//.setShowActionsInCompactView(0))
|
||||||
.setShowActionsInCompactView(0, 1, 2)
|
|
||||||
else
|
|
||||||
androidx.media.app.NotificationCompat.MediaStyle()
|
|
||||||
.setMediaSession(session.sessionToken)
|
|
||||||
.setShowActionsInCompactView(0))
|
|
||||||
.setDeleteIntent(deleteIntent)
|
.setDeleteIntent(deleteIntent)
|
||||||
.setChannelId(channel.id)
|
.setChannelId(channel.id)
|
||||||
|
|
||||||
|
@ -303,7 +298,7 @@ class MediaPlaybackService : Service() {
|
||||||
val notif = builder.build();
|
val notif = builder.build();
|
||||||
notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR;
|
notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR;
|
||||||
|
|
||||||
Logger.i(TAG, "Updating notification bitmap=${if (bitmap != null) "yes" else "no."} channelId=${channel.id} icon=${icon} video=${video?.name ?: ""} playWhenReady=${playWhenReady} session.sessionToken=${session.sessionToken}");
|
Logger.i(TAG, "Updating notification bitmap=${if (bitmap != null) "yes" else "no."} channelId=${channel.id} icon=${icon} video=${video?.name ?: ""} playWhenReady=${playWhenReady} session.sessionToken=${session.token}");
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
// For API 29 and above
|
// For API 29 and above
|
||||||
|
@ -316,20 +311,7 @@ class MediaPlaybackService : Service() {
|
||||||
_notif_last_bitmap = bitmap;
|
_notif_last_bitmap = bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMediaSessionPlaybackState(state: Int, pos: Long) {
|
fun updateMediaSessionPlaybackState() {
|
||||||
_mediaSession?.setPlaybackState(
|
|
||||||
PlaybackStateCompat.Builder()
|
|
||||||
.setActions(
|
|
||||||
PlaybackStateCompat.ACTION_SEEK_TO or
|
|
||||||
PlaybackStateCompat.ACTION_PLAY or
|
|
||||||
PlaybackStateCompat.ACTION_PAUSE or
|
|
||||||
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
|
|
||||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
|
|
||||||
PlaybackStateCompat.ACTION_PLAY_PAUSE
|
|
||||||
)
|
|
||||||
.setState(state, pos, 1f, SystemClock.elapsedRealtime())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
if(_focusRequest == null)
|
if(_focusRequest == null)
|
||||||
setAudioFocus();
|
setAudioFocus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.states
|
package com.futo.platformplayer.states
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.upstream.DefaultAllocator
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
||||||
|
@ -13,10 +17,6 @@ import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.models.Playlist
|
import com.futo.platformplayer.models.Playlist
|
||||||
import com.futo.platformplayer.services.MediaPlaybackService
|
import com.futo.platformplayer.services.MediaPlaybackService
|
||||||
import com.futo.platformplayer.video.PlayerManager
|
import com.futo.platformplayer.video.PlayerManager
|
||||||
import com.google.android.exoplayer2.C
|
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultAllocator
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
@ -107,8 +107,8 @@ class StatePlayer {
|
||||||
fun updateMediaSession(videoUpdated: IPlatformVideoDetails?) {
|
fun updateMediaSession(videoUpdated: IPlatformVideoDetails?) {
|
||||||
MediaPlaybackService.getService()?.updateMediaSession(videoUpdated);
|
MediaPlaybackService.getService()?.updateMediaSession(videoUpdated);
|
||||||
}
|
}
|
||||||
fun updateMediaSessionPlaybackState(state: Int, pos: Long) {
|
fun updateMediaSessionPlaybackState() {
|
||||||
MediaPlaybackService.getService()?.updateMediaSessionPlaybackState(state, pos);
|
MediaPlaybackService.getService()?.updateMediaSessionPlaybackState();
|
||||||
}
|
}
|
||||||
fun closeMediaSession() {
|
fun closeMediaSession() {
|
||||||
MediaPlaybackService.getService()?.closeMediaSession();
|
MediaPlaybackService.getService()?.closeMediaSession();
|
||||||
|
@ -571,6 +571,8 @@ class StatePlayer {
|
||||||
}
|
}
|
||||||
return _thumbnailExoPlayer!!;
|
return _thumbnailExoPlayer!!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun createExoPlayer(context : Context): ExoPlayer {
|
private fun createExoPlayer(context : Context): ExoPlayer {
|
||||||
return ExoPlayer.Builder(context)
|
return ExoPlayer.Builder(context)
|
||||||
.setLoadControl(
|
.setLoadControl(
|
||||||
|
@ -589,7 +591,6 @@ class StatePlayer {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun dispose(){
|
fun dispose(){
|
||||||
val player = _exoplayer;
|
val player = _exoplayer;
|
||||||
val thumbPlayer = _thumbnailExoPlayer;
|
val thumbPlayer = _thumbnailExoPlayer;
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.video
|
package com.futo.platformplayer.video
|
||||||
|
|
||||||
import android.media.session.PlaybackState
|
import androidx.media3.common.Player
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import androidx.media3.ui.PlayerView
|
||||||
import com.google.android.exoplayer2.Player
|
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
|
||||||
|
|
||||||
class PlayerManager {
|
class PlayerManager {
|
||||||
private var _currentView: StyledPlayerView? = null;
|
private var _currentView: PlayerView? = null;
|
||||||
private val _stateMap = HashMap<String, PlayerState>();
|
private val _stateMap = HashMap<String, PlayerState>();
|
||||||
private var _currentState: PlayerState? = null;
|
private var _currentState: PlayerState? = null;
|
||||||
val currentState: PlayerState get() {
|
val currentState: PlayerState get() {
|
||||||
|
@ -25,16 +21,8 @@ class PlayerManager {
|
||||||
this.player = exoPlayer;
|
this.player = exoPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaybackStateCompat() : Int {
|
|
||||||
return when(player.playbackState) {
|
|
||||||
ExoPlayer.STATE_READY -> if(player.playWhenReady) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED;
|
|
||||||
ExoPlayer.STATE_BUFFERING -> PlaybackState.STATE_BUFFERING;
|
|
||||||
else -> PlaybackState.STATE_NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun attach(view: StyledPlayerView, stateName: String) {
|
fun attach(view: PlayerView, stateName: String) {
|
||||||
if(view != _currentView) {
|
if(view != _currentView) {
|
||||||
_currentView?.player = null;
|
_currentView?.player = null;
|
||||||
switchState(stateName);
|
switchState(stateName);
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.views.casting
|
package com.futo.platformplayer.views.casting
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.session.PlaybackState
|
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -13,7 +9,11 @@ import android.widget.FrameLayout
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.ui.DefaultTimeBar
|
||||||
|
import androidx.media3.ui.TimeBar
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
|
@ -23,9 +23,6 @@ import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
import com.futo.platformplayer.toHumanTime
|
import com.futo.platformplayer.toHumanTime
|
||||||
import com.futo.platformplayer.views.behavior.GestureControlView
|
import com.futo.platformplayer.views.behavior.GestureControlView
|
||||||
import com.google.android.exoplayer2.ui.DefaultTimeBar
|
|
||||||
import com.google.android.exoplayer2.ui.TimeBar
|
|
||||||
import com.google.android.exoplayer2.ui.TimeBar.OnScrubListener
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -54,6 +51,7 @@ class CastView : ConstraintLayout {
|
||||||
val onMinimizeClick = Event0();
|
val onMinimizeClick = Event0();
|
||||||
val onSettingsClick = Event0();
|
val onSettingsClick = Event0();
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_cast, this, true);
|
LayoutInflater.from(context).inflate(R.layout.view_cast, this, true);
|
||||||
|
|
||||||
|
@ -83,7 +81,7 @@ class CastView : ConstraintLayout {
|
||||||
}
|
}
|
||||||
_buttonLoop.setImageResource(if(StatePlayer.instance.loopVideo) R.drawable.ic_loop_active else R.drawable.ic_loop);
|
_buttonLoop.setImageResource(if(StatePlayer.instance.loopVideo) R.drawable.ic_loop_active else R.drawable.ic_loop);
|
||||||
|
|
||||||
_timeBar.addListener(object : OnScrubListener {
|
_timeBar.addListener(object : TimeBar.OnScrubListener {
|
||||||
override fun onScrubStart(timeBar: TimeBar, position: Long) {
|
override fun onScrubStart(timeBar: TimeBar, position: Long) {
|
||||||
StateCasting.instance.videoSeekTo(position.toDouble());
|
StateCasting.instance.videoSeekTo(position.toDouble());
|
||||||
}
|
}
|
||||||
|
@ -149,11 +147,9 @@ class CastView : ConstraintLayout {
|
||||||
_buttonPlay.visibility = View.VISIBLE;
|
_buttonPlay.visibility = View.VISIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
val position = StateCasting.instance.activeDevice?.expectedCurrentTime?.times(1000.0)?.toLong();
|
|
||||||
|
|
||||||
if(StatePlayer.instance.hasMediaSession()) {
|
if(StatePlayer.instance.hasMediaSession()) {
|
||||||
StatePlayer.instance.updateMediaSession(null);
|
StatePlayer.instance.updateMediaSession(null);
|
||||||
StatePlayer.instance.updateMediaSessionPlaybackState(getPlaybackStateCompat(), (position ?: 0));
|
StatePlayer.instance.updateMediaSessionPlaybackState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +179,7 @@ class CastView : ConstraintLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun setVideoDetails(video: IPlatformVideoDetails, position: Long) {
|
fun setVideoDetails(video: IPlatformVideoDetails, position: Long) {
|
||||||
Glide.with(_thumbnail)
|
Glide.with(_thumbnail)
|
||||||
.load(video.thumbnails.getHQThumbnail())
|
.load(video.thumbnails.getHQThumbnail())
|
||||||
|
@ -194,10 +191,11 @@ class CastView : ConstraintLayout {
|
||||||
_timeBar.setDuration(video.duration);
|
_timeBar.setDuration(video.duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun setTime(ms: Long) {
|
fun setTime(ms: Long) {
|
||||||
_textPosition.text = ms.toHumanTime(true);
|
_textPosition.text = ms.toHumanTime(true);
|
||||||
_timeBar.setPosition(ms / 1000);
|
_timeBar.setPosition(ms / 1000);
|
||||||
StatePlayer.instance.updateMediaSessionPlaybackState(getPlaybackStateCompat(), ms);
|
StatePlayer.instance.updateMediaSessionPlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
|
@ -207,13 +205,4 @@ class CastView : ConstraintLayout {
|
||||||
_updateTimeJob = null;
|
_updateTimeJob = null;
|
||||||
_scope.cancel();
|
_scope.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlaybackStateCompat(): Int {
|
|
||||||
val d = StateCasting.instance.activeDevice ?: return PlaybackState.STATE_NONE;
|
|
||||||
|
|
||||||
return when(d.isPlaying) {
|
|
||||||
true -> PlaybackStateCompat.STATE_PLAYING;
|
|
||||||
else -> PlaybackStateCompat.STATE_PAUSED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.views.video
|
package com.futo.platformplayer.views.video
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -9,6 +7,10 @@ import android.view.View
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.ui.PlayerControlView
|
||||||
|
import androidx.media3.ui.PlayerView
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.Settings
|
import com.futo.platformplayer.Settings
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
|
@ -17,8 +19,6 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.helpers.VideoHelper
|
import com.futo.platformplayer.helpers.VideoHelper
|
||||||
import com.futo.platformplayer.toHumanTime
|
import com.futo.platformplayer.toHumanTime
|
||||||
import com.futo.platformplayer.video.PlayerManager
|
import com.futo.platformplayer.video.PlayerManager
|
||||||
import com.google.android.exoplayer2.ui.PlayerControlView
|
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
|
||||||
|
|
||||||
|
|
||||||
class FutoThumbnailPlayer : FutoVideoPlayerBase {
|
class FutoThumbnailPlayer : FutoVideoPlayerBase {
|
||||||
|
@ -28,7 +28,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Views
|
//Views
|
||||||
private val videoView : StyledPlayerView;
|
private val videoView : PlayerView;
|
||||||
private val videoControls : PlayerControlView;
|
private val videoControls : PlayerControlView;
|
||||||
private val buttonMute : ImageButton;
|
private val buttonMute : ImageButton;
|
||||||
private val buttonUnMute : ImageButton;
|
private val buttonUnMute : ImageButton;
|
||||||
|
@ -41,6 +41,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
|
||||||
private val _evMuteChanged = mutableListOf<(FutoThumbnailPlayer, Boolean)->Unit>();
|
private val _evMuteChanged = mutableListOf<(FutoThumbnailPlayer, Boolean)->Unit>();
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
|
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
|
||||||
LayoutInflater.from(context).inflate(R.layout.thumbnail_video_view, this, true);
|
LayoutInflater.from(context).inflate(R.layout.thumbnail_video_view, this, true);
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun setPlayer(player: PlayerManager?){
|
fun setPlayer(player: PlayerManager?){
|
||||||
changePlayer(player);
|
changePlayer(player);
|
||||||
player?.attach(videoView, PLAYER_STATE_NAME);
|
player?.attach(videoView, PLAYER_STATE_NAME);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.views.video
|
package com.futo.platformplayer.views.video
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -15,6 +13,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.setMargins
|
import androidx.core.view.setMargins
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
|
@ -31,13 +30,14 @@ import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
import com.futo.platformplayer.views.behavior.GestureControlView
|
import com.futo.platformplayer.views.behavior.GestureControlView
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import androidx.media3.common.PlaybackParameters
|
||||||
import com.google.android.exoplayer2.PlaybackParameters
|
import androidx.media3.common.VideoSize
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.google.android.exoplayer2.ui.PlayerControlView
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import com.google.android.exoplayer2.ui.TimeBar
|
import androidx.media3.ui.PlayerControlView
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
import androidx.media3.ui.PlayerView
|
||||||
|
import androidx.media3.ui.TimeBar
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -58,7 +58,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
|
|
||||||
//Views
|
//Views
|
||||||
private val _root: ConstraintLayout;
|
private val _root: ConstraintLayout;
|
||||||
private val _videoView: StyledPlayerView;
|
private val _videoView: PlayerView;
|
||||||
|
|
||||||
val videoControls: PlayerControlView;
|
val videoControls: PlayerControlView;
|
||||||
private val _videoControls_fullscreen: PlayerControlView;
|
private val _videoControls_fullscreen: PlayerControlView;
|
||||||
|
@ -127,6 +127,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
val onVideoClicked = Event0();
|
val onVideoClicked = Event0();
|
||||||
val onTimeBarChanged = Event2<Long, Long>();
|
val onTimeBarChanged = Event2<Long, Long>();
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
|
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
|
||||||
LayoutInflater.from(context).inflate(R.layout.video_view, this, true);
|
LayoutInflater.from(context).inflate(R.layout.video_view, this, true);
|
||||||
_root = findViewById(R.id.videoview_root);
|
_root = findViewById(R.id.videoview_root);
|
||||||
|
@ -139,8 +140,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
_control_rotate_lock = videoControls.findViewById(R.id.exo_rotate_lock);
|
_control_rotate_lock = videoControls.findViewById(R.id.exo_rotate_lock);
|
||||||
_control_loop = videoControls.findViewById(R.id.exo_loop);
|
_control_loop = videoControls.findViewById(R.id.exo_loop);
|
||||||
_control_cast = videoControls.findViewById(R.id.exo_cast);
|
_control_cast = videoControls.findViewById(R.id.exo_cast);
|
||||||
_control_play = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play);
|
_control_play = videoControls.findViewById(androidx.media3.ui.R.id.exo_play);
|
||||||
_time_bar = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
|
_time_bar = videoControls.findViewById(androidx.media3.ui.R.id.exo_progress);
|
||||||
_control_chapter = videoControls.findViewById(R.id.text_chapter_current);
|
_control_chapter = videoControls.findViewById(R.id.text_chapter_current);
|
||||||
_buttonNext = videoControls.findViewById(R.id.button_next);
|
_buttonNext = videoControls.findViewById(R.id.button_next);
|
||||||
_buttonPrevious = videoControls.findViewById(R.id.button_previous);
|
_buttonPrevious = videoControls.findViewById(R.id.button_previous);
|
||||||
|
@ -152,9 +153,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
_control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_rotate_lock);
|
_control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_rotate_lock);
|
||||||
_control_loop_fullscreen = videoControls.findViewById(R.id.exo_loop);
|
_control_loop_fullscreen = videoControls.findViewById(R.id.exo_loop);
|
||||||
_control_cast_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_cast);
|
_control_cast_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_cast);
|
||||||
_control_play_fullscreen = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play);
|
_control_play_fullscreen = videoControls.findViewById(androidx.media3.ui.R.id.exo_play);
|
||||||
_control_chapter_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_chapter_current);
|
_control_chapter_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_chapter_current);
|
||||||
_time_bar_fullscreen = _videoControls_fullscreen.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
|
_time_bar_fullscreen = _videoControls_fullscreen.findViewById(androidx.media3.ui.R.id.exo_progress);
|
||||||
_buttonPrevious_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_previous);
|
_buttonPrevious_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_previous);
|
||||||
_buttonNext_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_next);
|
_buttonNext_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_next);
|
||||||
|
|
||||||
|
@ -404,14 +405,15 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun setArtwork(drawable: Drawable?) {
|
fun setArtwork(drawable: Drawable?) {
|
||||||
if (drawable != null) {
|
if (drawable != null) {
|
||||||
_videoView.defaultArtwork = drawable;
|
_videoView.defaultArtwork = drawable;
|
||||||
_videoView.artworkDisplayMode = StyledPlayerView.ARTWORK_DISPLAY_MODE_FILL;
|
_videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_FILL;
|
||||||
fitOrFill(isFullScreen);
|
fitOrFill(isFullScreen);
|
||||||
} else {
|
} else {
|
||||||
_videoView.defaultArtwork = null;
|
_videoView.defaultArtwork = null;
|
||||||
_videoView.artworkDisplayMode = StyledPlayerView.ARTWORK_DISPLAY_MODE_OFF;
|
_videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_OFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,6 +438,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
return exoPlayer?.player?.playbackParameters?.speed ?: 1.0f;
|
return exoPlayer?.player?.playbackParameters?.speed ?: 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun setFullScreen(fullScreen: Boolean) {
|
fun setFullScreen(fullScreen: Boolean) {
|
||||||
if (isFullScreen == fullScreen) {
|
if (isFullScreen == fullScreen) {
|
||||||
return;
|
return;
|
||||||
|
@ -538,6 +541,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Sizing
|
//Sizing
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun fitHeight(videoSize : VideoSize? = null){
|
fun fitHeight(videoSize : VideoSize? = null){
|
||||||
Logger.i(TAG, "Video Fit Height");
|
Logger.i(TAG, "Video Fit Height");
|
||||||
if(_originalBottomMargin != 0) {
|
if(_originalBottomMargin != 0) {
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.futo.platformplayer.views.video
|
package com.futo.platformplayer.views.video
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.PlaybackException
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.VideoSize
|
||||||
|
import androidx.media3.common.text.CueGroup
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.datasource.DefaultDataSource
|
||||||
|
import androidx.media3.datasource.DefaultHttpDataSource
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.dash.DashMediaSource
|
||||||
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||||
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||||
|
import androidx.media3.exoplayer.source.SingleSampleMediaSource
|
||||||
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import com.futo.platformplayer.Settings
|
import com.futo.platformplayer.Settings
|
||||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||||
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
||||||
|
@ -28,22 +44,6 @@ import com.futo.platformplayer.helpers.VideoHelper
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.video.PlayerManager
|
import com.futo.platformplayer.video.PlayerManager
|
||||||
import com.google.android.exoplayer2.C
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
|
||||||
import com.google.android.exoplayer2.MediaItem
|
|
||||||
import com.google.android.exoplayer2.PlaybackException
|
|
||||||
import com.google.android.exoplayer2.Player
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource
|
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource
|
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
|
||||||
import com.google.android.exoplayer2.source.SingleSampleMediaSource
|
|
||||||
import com.google.android.exoplayer2.source.dash.DashMediaSource
|
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
|
||||||
import com.google.android.exoplayer2.text.CueGroup
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -251,6 +251,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
_targetTrackAudioBitrate = bitrate;
|
_targetTrackAudioBitrate = bitrate;
|
||||||
updateTrackSelector();
|
updateTrackSelector();
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun updateTrackSelector() {
|
private fun updateTrackSelector() {
|
||||||
var builder = DefaultTrackSelector.Parameters.Builder(context);
|
var builder = DefaultTrackSelector.Parameters.Builder(context);
|
||||||
if(_targetTrackVideoHeight > 0) {
|
if(_targetTrackVideoHeight > 0) {
|
||||||
|
@ -298,6 +299,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
return loadSelectedSources(play, resume);
|
return loadSelectedSources(play, resume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun swapSubtitles(scope: CoroutineScope, subtitles: ISubtitleSource?) {
|
fun swapSubtitles(scope: CoroutineScope, subtitles: ISubtitleSource?) {
|
||||||
if(subtitles == null)
|
if(subtitles == null)
|
||||||
clearSubtitles();
|
clearSubtitles();
|
||||||
|
@ -369,6 +371,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Video loads
|
//Video loads
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceLocal(videoSource: LocalVideoSource) {
|
private fun swapVideoSourceLocal(videoSource: LocalVideoSource) {
|
||||||
Logger.i(TAG, "Loading VideoSource [Local]");
|
Logger.i(TAG, "Loading VideoSource [Local]");
|
||||||
val file = File(videoSource.filePath);
|
val file = File(videoSource.filePath);
|
||||||
|
@ -377,14 +380,13 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceUrlRange(videoSource: JSVideoUrlRangeSource) {
|
private fun swapVideoSourceUrlRange(videoSource: JSVideoUrlRangeSource) {
|
||||||
Logger.i(TAG, "Loading JSVideoUrlRangeSource");
|
Logger.i(TAG, "Loading JSVideoUrlRangeSource");
|
||||||
if(videoSource.hasItag) {
|
if(videoSource.hasItag) {
|
||||||
//Temporary workaround for Youtube
|
//Temporary workaround for Youtube
|
||||||
try {
|
try {
|
||||||
_lastVideoMediaSource = VideoHelper.convertItagSourceToChunkedDashSource(videoSource);
|
_lastVideoMediaSource = VideoHelper.convertItagSourceToChunkedDashSource(videoSource);
|
||||||
if(_lastVideoMediaSource == null)
|
|
||||||
throw java.lang.IllegalStateException("Dash manifest workaround failed");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//If it fails to create the dash workaround, fallback to standard progressive
|
//If it fails to create the dash workaround, fallback to standard progressive
|
||||||
|
@ -397,18 +399,21 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
}
|
}
|
||||||
else throw IllegalArgumentException("source without itag data...");
|
else throw IllegalArgumentException("source without itag data...");
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceUrl(videoSource: IVideoUrlSource) {
|
private fun swapVideoSourceUrl(videoSource: IVideoUrlSource) {
|
||||||
Logger.i(TAG, "Loading VideoSource [Url]");
|
Logger.i(TAG, "Loading VideoSource [Url]");
|
||||||
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory()
|
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory()
|
||||||
.setUserAgent(DEFAULT_USER_AGENT))
|
.setUserAgent(DEFAULT_USER_AGENT))
|
||||||
.createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl()));
|
.createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl()));
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceDash(videoSource: IDashManifestSource) {
|
private fun swapVideoSourceDash(videoSource: IDashManifestSource) {
|
||||||
Logger.i(TAG, "Loading VideoSource [Dash]");
|
Logger.i(TAG, "Loading VideoSource [Dash]");
|
||||||
_lastVideoMediaSource = DashMediaSource.Factory(DefaultHttpDataSource.Factory()
|
_lastVideoMediaSource = DashMediaSource.Factory(DefaultHttpDataSource.Factory()
|
||||||
.setUserAgent(DEFAULT_USER_AGENT))
|
.setUserAgent(DEFAULT_USER_AGENT))
|
||||||
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) {
|
private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) {
|
||||||
Logger.i(TAG, "Loading VideoSource [HLS]");
|
Logger.i(TAG, "Loading VideoSource [HLS]");
|
||||||
_lastVideoMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()
|
_lastVideoMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()
|
||||||
|
@ -416,7 +421,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
.createMediaSource(MediaItem.fromUri(videoSource.url));
|
.createMediaSource(MediaItem.fromUri(videoSource.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Audio loads
|
//Audio loads
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceLocal(audioSource: LocalAudioSource) {
|
private fun swapAudioSourceLocal(audioSource: LocalAudioSource) {
|
||||||
Logger.i(TAG, "Loading AudioSource [Local]");
|
Logger.i(TAG, "Loading AudioSource [Local]");
|
||||||
val file = File(audioSource.filePath);
|
val file = File(audioSource.filePath);
|
||||||
|
@ -425,6 +432,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceUrlRange(audioSource: JSAudioUrlRangeSource) {
|
private fun swapAudioSourceUrlRange(audioSource: JSAudioUrlRangeSource) {
|
||||||
Logger.i(TAG, "Loading JSAudioUrlRangeSource");
|
Logger.i(TAG, "Loading JSAudioUrlRangeSource");
|
||||||
if(audioSource.hasItag) {
|
if(audioSource.hasItag) {
|
||||||
|
@ -444,12 +452,14 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
}
|
}
|
||||||
else throw IllegalArgumentException("source without itag data...")
|
else throw IllegalArgumentException("source without itag data...")
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceUrl(audioSource: IAudioUrlSource) {
|
private fun swapAudioSourceUrl(audioSource: IAudioUrlSource) {
|
||||||
Logger.i(TAG, "Loading AudioSource [Url]");
|
Logger.i(TAG, "Loading AudioSource [Url]");
|
||||||
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory()
|
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory()
|
||||||
.setUserAgent(DEFAULT_USER_AGENT))
|
.setUserAgent(DEFAULT_USER_AGENT))
|
||||||
.createMediaSource(MediaItem.fromUri(audioSource.getAudioUrl()));
|
.createMediaSource(MediaItem.fromUri(audioSource.getAudioUrl()));
|
||||||
}
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceHLS(audioSource: IHLSManifestAudioSource) {
|
private fun swapAudioSourceHLS(audioSource: IHLSManifestAudioSource) {
|
||||||
Logger.i(TAG, "Loading AudioSource [HLS]");
|
Logger.i(TAG, "Loading AudioSource [HLS]");
|
||||||
_lastAudioMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()
|
_lastAudioMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()
|
||||||
|
@ -479,6 +489,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
return VideoHelper.selectBestAudioSource(video.video, PREFERED_AUDIO_CONTAINERS, preferredLanguage);
|
return VideoHelper.selectBestAudioSource(video.video, PREFERED_AUDIO_CONTAINERS, preferredLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun loadSelectedSources(play: Boolean, resume: Boolean): Boolean {
|
private fun loadSelectedSources(play: Boolean, resume: Boolean): Boolean {
|
||||||
val sourceVideo = if(!isAudioMode || _lastAudioMediaSource == null) _lastVideoMediaSource else null;
|
val sourceVideo = if(!isAudioMode || _lastAudioMediaSource == null) _lastVideoMediaSource else null;
|
||||||
val sourceAudio = _lastAudioMediaSource;
|
val sourceAudio = _lastAudioMediaSource;
|
||||||
|
@ -506,11 +517,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun reloadMediaSource(play: Boolean = false, resume: Boolean = true) {
|
private fun reloadMediaSource(play: Boolean = false, resume: Boolean = true) {
|
||||||
val player = exoPlayer
|
val player = exoPlayer ?: return
|
||||||
if (player == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
val positionBefore = player.player.currentPosition;
|
val positionBefore = player.player.currentPosition;
|
||||||
if(_mediaSource != null) {
|
if(_mediaSource != null) {
|
||||||
player.player.setMediaSource(_mediaSource!!);
|
player.player.setMediaSource(_mediaSource!!);
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
package com.futo.platformplayer.views.video.datasources;
|
package com.futo.platformplayer.views.video.datasources;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
import static androidx.media3.datasource.HttpUtil.buildRangeRequestHeader;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier;
|
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier;
|
||||||
import com.google.android.exoplayer2.C;
|
import androidx.media3.common.C;
|
||||||
import com.google.android.exoplayer2.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import com.google.android.exoplayer2.upstream.BaseDataSource;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.android.exoplayer2.upstream.DataSourceException;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import androidx.media3.datasource.BaseDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
|
import androidx.media3.datasource.DataSourceException;
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import androidx.media3.datasource.DataSpec;
|
||||||
import com.google.android.exoplayer2.upstream.HttpUtil;
|
import androidx.media3.datasource.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import androidx.media3.datasource.HttpUtil;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import androidx.media3.datasource.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ForwardingMap;
|
import com.google.common.collect.ForwardingMap;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
@ -45,6 +47,7 @@ import java.util.zip.GZIPInputStream;
|
||||||
* Based on the default ExoPlayer DefaultHttpDataSource
|
* Based on the default ExoPlayer DefaultHttpDataSource
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
public static final class Factory implements HttpDataSource.Factory {
|
public static final class Factory implements HttpDataSource.Factory {
|
||||||
|
|
||||||
|
@ -142,7 +145,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
/**
|
/**
|
||||||
* Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a
|
* Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a
|
||||||
* {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
|
* {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
|
||||||
* JSHttpDataSource#open(com.google.android.exoplayer2.upstream.DataSpec)}.
|
* JSHttpDataSource#open(androidx.media3.datasource.DataSpec)}.
|
||||||
*
|
*
|
||||||
* <p>The default is {@code null}.
|
* <p>The default is {@code null}.
|
||||||
*
|
*
|
||||||
|
@ -160,7 +163,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
*
|
*
|
||||||
* <p>The default is {@code null}.
|
* <p>The default is {@code null}.
|
||||||
*
|
*
|
||||||
* <p>See {@link com.google.android.exoplayer2.upstream.DataSource#addTransferListener(TransferListener)}.
|
* <p>See {@link androidx.media3.datasource.DataSource#addTransferListener(TransferListener)}.
|
||||||
*
|
*
|
||||||
* @param transferListener The listener that will be used.
|
* @param transferListener The listener that will be used.
|
||||||
* @return This factory.
|
* @return This factory.
|
||||||
|
@ -367,12 +370,12 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
if (dataSpec.length != C.LENGTH_UNSET) {
|
if (dataSpec.length != C.LENGTH_UNSET) {
|
||||||
bytesToRead = dataSpec.length;
|
bytesToRead = dataSpec.length;
|
||||||
} else {
|
} else {
|
||||||
long contentLength =
|
long contentLength = HttpUtil.getContentLength(
|
||||||
HttpUtil.getContentLength(
|
|
||||||
connection.getHeaderField(HttpHeaders.CONTENT_LENGTH),
|
connection.getHeaderField(HttpHeaders.CONTENT_LENGTH),
|
||||||
connection.getHeaderField(HttpHeaders.CONTENT_RANGE));
|
connection.getHeaderField(HttpHeaders.CONTENT_RANGE)
|
||||||
bytesToRead =
|
);
|
||||||
contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET;
|
|
||||||
|
bytesToRead = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Gzip is enabled. If the server opts to use gzip then the content length in the response
|
// Gzip is enabled. If the server opts to use gzip then the content length in the response
|
||||||
|
@ -457,7 +460,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
/** Establishes a connection, following redirects to do so where permitted. */
|
/** Establishes a connection, following redirects to do so where permitted. */
|
||||||
private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
|
private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
|
||||||
URL url = new URL(dataSpec.uri.toString());
|
URL url = new URL(dataSpec.uri.toString());
|
||||||
@HttpMethod int httpMethod = dataSpec.httpMethod;
|
@DataSpec.HttpMethod int httpMethod = dataSpec.httpMethod;
|
||||||
@Nullable byte[] httpBody = dataSpec.httpBody;
|
@Nullable byte[] httpBody = dataSpec.httpBody;
|
||||||
long position = dataSpec.position;
|
long position = dataSpec.position;
|
||||||
long length = dataSpec.length;
|
long length = dataSpec.length;
|
||||||
|
@ -543,7 +546,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||||
*/
|
*/
|
||||||
private HttpURLConnection makeConnection(
|
private HttpURLConnection makeConnection(
|
||||||
URL url,
|
URL url,
|
||||||
@HttpMethod int httpMethod,
|
@DataSpec.HttpMethod int httpMethod,
|
||||||
@Nullable byte[] httpBody,
|
@Nullable byte[] httpBody,
|
||||||
long position,
|
long position,
|
||||||
long length,
|
long length,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<com.google.android.exoplayer2.ui.StyledPlayerView
|
<com.google.android.exoplayer2.ui.PlayerView
|
||||||
android:id="@+id/video_player"
|
android:id="@+id/video_player"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
android:id="@+id/videoview_root"
|
android:id="@+id/videoview_root"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<com.google.android.exoplayer2.ui.StyledPlayerView
|
<com.google.android.exoplayer2.ui.PlayerView
|
||||||
android:id="@+id/video_player"
|
android:id="@+id/video_player"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue