Media3 migration.

This commit is contained in:
Koen 2023-12-11 17:54:18 +01:00
parent fa12f8277c
commit 3b8d256bad
15 changed files with 213 additions and 239 deletions

View file

@ -172,13 +172,14 @@ dependencies {
implementation("com.caoccao.javet:javet-android:2.2.1")
//Exoplayer
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-transformer:2.19.1'
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation 'androidx.media3:media3-exoplayer-dash:1.2.0'
implementation 'androidx.media3:media3-ui:1.2.0'
implementation 'androidx.media3:media3-session:1.2.0'
implementation 'androidx.media3:media3-exoplayer-hls:1.2.0'
implementation 'androidx.media3:media3-exoplayer-rtsp:1.2.0'
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-ui-ktx:2.7.5'

View file

@ -1,7 +1,7 @@
@file:Suppress("DEPRECATION")
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.reference.V8ValueObject
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.orNull
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 {
protected val _config: IV8PluginConfig;

View file

@ -1,5 +1,3 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.fragment.mainactivity.main
import android.app.PictureInPictureParams
@ -14,7 +12,6 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.net.Uri
import android.support.v4.media.session.PlaybackStateCompat
import android.text.Spanned
import android.util.AttributeSet
import android.util.Log
@ -32,6 +29,12 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
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.request.target.CustomTarget
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.Opinion
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -155,7 +153,6 @@ import java.time.OffsetDateTime
import kotlin.math.abs
import kotlin.math.roundToLong
class VideoDetailView : ConstraintLayout {
private val TAG = "VideoDetailView"
@ -303,7 +300,7 @@ class VideoDetailView : ConstraintLayout {
Pair(0, 10) //around live, try every 10 seconds
);
@androidx.annotation.OptIn(UnstableApi::class)
constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.fragview_video_detail, this);
@ -312,7 +309,7 @@ class VideoDetailView : ConstraintLayout {
_cast = findViewById(R.id.videodetail_cast);
_player = findViewById(R.id.videodetail_player);
_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);
_subTitle = findViewById(R.id.videodetail_meta);
_platform = findViewById(R.id.videodetail_platform);
@ -574,8 +571,8 @@ class VideoDetailView : ConstraintLayout {
}
_playerProgress.player = _player.exoPlayer?.player;
_playerProgress.setProgressUpdateListener { position, _ ->
StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, position);
_playerProgress.setProgressUpdateListener { _, _ ->
StatePlayer.instance.updateMediaSessionPlaybackState();
}
StatePlayer.instance.onQueueChanged.subscribe(this) {
@ -1497,6 +1494,7 @@ class VideoDetailView : ConstraintLayout {
}
//Events
@androidx.annotation.OptIn(UnstableApi::class)
private fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean){
Logger.i(TAG, "onSourceChanged(videoSource=$videoSource, audioSource=$audioSource, resume=$resume)")
@ -1532,7 +1530,7 @@ class VideoDetailView : ConstraintLayout {
private var _didTriggerDatasourceError = false;
private fun onDataSourceError(exception: Throwable) {
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
if(currentVideo == null || currentVideo !is IPluginSourced)
return;
@ -1611,6 +1609,7 @@ class VideoDetailView : ConstraintLayout {
val v = video ?: return;
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) {
Logger.i(TAG, "updateQualitySourcesOverlay");
@ -1730,7 +1729,7 @@ class VideoDetailView : ConstraintLayout {
{ handleSelectAudioTrack(it) });
}.toList().toTypedArray())
else null,
if(video?.subtitles?.isNotEmpty() ?: false && video != null)
if(video?.subtitles?.isNotEmpty() == true)
SlideUpMenuGroup(this.context, context.getString(R.string.subtitles), "subtitles",
*video.subtitles
.map {
@ -1792,7 +1791,7 @@ class VideoDetailView : ConstraintLayout {
_cast.setIsPlaying(playing);
} else {
StatePlayer.instance.updateMediaSession( null);
StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, _player.exoPlayer?.player?.currentPosition ?: 0);
StatePlayer.instance.updateMediaSessionPlaybackState();
}
if(playing) {

View file

@ -1,8 +1,7 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.helpers
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.VideoUnMuxedSourceDescriptor
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.JSVideoUrlRangeSource
import com.futo.platformplayer.logging.Logger
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.dash.DashMediaSource
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
import com.google.android.exoplayer2.upstream.ResolvingDataSource
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.ResolvingDataSource
import androidx.media3.exoplayer.dash.DashMediaSource
import androidx.media3.exoplayer.dash.manifest.DashManifestParser
import androidx.media3.exoplayer.source.MediaSource
import kotlin.math.abs
class VideoHelper {
@ -121,7 +121,7 @@ class VideoHelper {
return bestSource;
}
@Suppress("DEPRECATION")
@OptIn(UnstableApi::class)
fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource {
val urlToUse = videoSource.getVideoUrl();
val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse,
@ -140,14 +140,13 @@ class VideoHelper {
);
val manifest = DashManifestParser().parse(Uri.parse(""), manifestConfig.byteInputStream());
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);
return@Resolver dataSpec;
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build())
}
@Suppress("DEPRECATION")
@OptIn(UnstableApi::class)
fun convertItagSourceToChunkedDashSource(audioSource: JSAudioUrlRangeSource) : MediaSource {
val manifestConfig = ProgressiveDashManifestCreator.fromAudioProgressiveStreamingUrl(audioSource.getAudioUrl(),
audioSource.duration?.times(1000) ?: 0,

View file

@ -17,7 +17,6 @@ class InstallReceiver : BroadcastReceiver() {
val onReceiveResult = Event1<String?>();
}
@Suppress("DEPRECATION")
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1);
Logger.i(TAG, "Received status $status.");

View file

@ -13,15 +13,14 @@ import android.graphics.drawable.Drawable
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.media.AudioManager.OnAudioFocusChangeListener
import android.media.MediaMetadata
import android.os.Build
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 androidx.annotation.OptIn
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.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
@ -51,7 +50,7 @@ class MediaPlaybackService : Service() {
private var _audioManager: AudioManager? = null;
private var _notificationManager: NotificationManager? = null;
private var _notificationChannel: NotificationChannel? = null;
private var _mediaSession: MediaSessionCompat? = null;
private var _mediaSession: MediaSession? = null;
private var _hasFocus: Boolean = false;
private var _focusRequest: AudioFocusRequest? = null;
private var _audioFocusLossTime_ms: Long? = null
@ -82,6 +81,7 @@ class MediaPlaybackService : Service() {
return START_STICKY;
}
@OptIn(UnstableApi::class)
fun setupNotificationRequirements() {
_audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager;
_notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
@ -91,47 +91,54 @@ class MediaPlaybackService : Service() {
};
_notificationManager!!.createNotificationChannel(_notificationChannel!!);
_mediaSession = MediaSessionCompat(this, "PlayerState");
_mediaSession?.setPlaybackState(PlaybackStateCompat.Builder()
_mediaSession = MediaSession.Builder(this, StatePlayer.instance.getPlayerOrCreate(this).player)
.setCallback(object: MediaSession.Callback {
override fun onMediaButtonEvent(session: MediaSession, controllerInfo: MediaSession.ControllerInfo, intent: Intent): Boolean {
//TODO: Reimplement
return super.onMediaButtonEvent(session, controllerInfo, intent)
}
/*override fun onSeekTo(pos: Long) {
super.onSeekTo(pos)
Logger.i(TAG, "Media session callback onSeekTo(pos = $pos)");
MediaControlReceiver.onSeekToReceived.emit(pos);
}
override fun onPlay() {
super.onPlay();
Logger.i(TAG, "Media session callback onPlay()");
MediaControlReceiver.onPlayReceived.emit();
}
override fun onPause() {
super.onPause();
Logger.i(TAG, "Media session callback onPause()");
MediaControlReceiver.onPauseReceived.emit();
}
override fun onStop() {
super.onStop();
Logger.i(TAG, "Media session callback onStop()");
MediaControlReceiver.onCloseReceived.emit();
}
override fun onSkipToPrevious() {
super.onSkipToPrevious();
Logger.i(TAG, "Media session callback onSkipToPrevious()");
MediaControlReceiver.onPreviousReceived.emit();
}
override fun onSkipToNext() {
super.onSkipToNext()
Logger.i(TAG, "Media session callback onSkipToNext()");
MediaControlReceiver.onNextReceived.emit();
}*/
})
.build();
/*_mediaSession?.setPlaybackState(PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1f)
.build());
_mediaSession?.setCallback(object: MediaSessionCompat.Callback() {
override fun onSeekTo(pos: Long) {
super.onSeekTo(pos)
Logger.i(TAG, "Media session callback onSeekTo(pos = $pos)");
MediaControlReceiver.onSeekToReceived.emit(pos);
}
override fun onPlay() {
super.onPlay();
Logger.i(TAG, "Media session callback onPlay()");
MediaControlReceiver.onPlayReceived.emit();
}
override fun onPause() {
super.onPause();
Logger.i(TAG, "Media session callback onPause()");
MediaControlReceiver.onPauseReceived.emit();
}
override fun onStop() {
super.onStop();
Logger.i(TAG, "Media session callback onStop()");
MediaControlReceiver.onCloseReceived.emit();
}
override fun onSkipToPrevious() {
super.onSkipToPrevious();
Logger.i(TAG, "Media session callback onSkipToPrevious()");
MediaControlReceiver.onPreviousReceived.emit();
}
override fun onSkipToNext() {
super.onSkipToNext()
Logger.i(TAG, "Media session callback onSkipToNext()");
MediaControlReceiver.onNextReceived.emit();
}
});
.build());*/
}
override fun onCreate() {
@ -186,15 +193,7 @@ class MediaPlaybackService : Service() {
if(_notificationChannel == null || _mediaSession == null)
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();
_notif_last_video = video;
if(isUpdating)
@ -221,6 +220,7 @@ class MediaPlaybackService : Service() {
private fun generateMediaAction(icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action {
return NotificationCompat.Action.Builder(icon, title, intent).build();
}
@OptIn(UnstableApi::class)
private fun notifyMediaSession(video: IPlatformVideo?, desiredBitmap: Bitmap?) {
val channel = _notificationChannel ?: return;
val session = _mediaSession ?: return;
@ -249,14 +249,9 @@ class MediaPlaybackService : Service() {
.setOngoing(true)
.setSilent(true)
.setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE))
.setStyle(if(hasQueue)
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(session.sessionToken)
.setShowActionsInCompactView(0, 1, 2)
else
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(session.sessionToken)
.setShowActionsInCompactView(0))
.setStyle(
if(hasQueue) MediaStyleNotificationHelper.MediaStyle(session)//.setShowActionsInCompactView(0, 1, 2)
else MediaStyleNotificationHelper.MediaStyle(session))//.setShowActionsInCompactView(0))
.setDeleteIntent(deleteIntent)
.setChannelId(channel.id)
@ -303,7 +298,7 @@ class MediaPlaybackService : Service() {
val notif = builder.build();
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) {
// For API 29 and above
@ -316,20 +311,7 @@ class MediaPlaybackService : Service() {
_notif_last_bitmap = bitmap;
}
fun updateMediaSessionPlaybackState(state: Int, pos: Long) {
_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());
fun updateMediaSessionPlaybackState() {
if(_focusRequest == null)
setAudioFocus();
}

View file

@ -1,8 +1,12 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.states
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.UIDialogs
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.services.MediaPlaybackService
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
/***
@ -107,8 +107,8 @@ class StatePlayer {
fun updateMediaSession(videoUpdated: IPlatformVideoDetails?) {
MediaPlaybackService.getService()?.updateMediaSession(videoUpdated);
}
fun updateMediaSessionPlaybackState(state: Int, pos: Long) {
MediaPlaybackService.getService()?.updateMediaSessionPlaybackState(state, pos);
fun updateMediaSessionPlaybackState() {
MediaPlaybackService.getService()?.updateMediaSessionPlaybackState();
}
fun closeMediaSession() {
MediaPlaybackService.getService()?.closeMediaSession();
@ -557,21 +557,23 @@ class StatePlayer {
}
//Player Initialization
fun getPlayerOrCreate(context : Context) : PlayerManager {
fun getPlayerOrCreate(context: Context) : PlayerManager {
if(_exoplayer == null) {
val player = createExoPlayer(context);
_exoplayer = PlayerManager(player);
}
return _exoplayer!!;
}
fun getThumbnailPlayerOrCreate(context : Context) : PlayerManager {
fun getThumbnailPlayerOrCreate(context: Context) : PlayerManager {
if(_thumbnailExoPlayer == null) {
val player = createExoPlayer(context);
_thumbnailExoPlayer = PlayerManager(player);
}
return _thumbnailExoPlayer!!;
}
private fun createExoPlayer(context : Context) : ExoPlayer {
@OptIn(UnstableApi::class)
private fun createExoPlayer(context : Context): ExoPlayer {
return ExoPlayer.Builder(context)
.setLoadControl(
DefaultLoadControl.Builder()
@ -589,7 +591,6 @@ class StatePlayer {
.build();
}
fun dispose(){
val player = _exoplayer;
val thumbPlayer = _thumbnailExoPlayer;

View file

@ -1,15 +1,11 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.video
import android.media.session.PlaybackState
import android.support.v4.media.session.PlaybackStateCompat
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.StyledPlayerView
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
class PlayerManager {
private var _currentView: StyledPlayerView? = null;
private var _currentView: PlayerView? = null;
private val _stateMap = HashMap<String, PlayerState>();
private var _currentState: PlayerState? = null;
val currentState: PlayerState get() {
@ -25,16 +21,8 @@ class PlayerManager {
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
fun attach(view: StyledPlayerView, stateName: String) {
fun attach(view: PlayerView, stateName: String) {
if(view != _currentView) {
_currentView?.player = null;
switchState(stateName);

View file

@ -1,10 +1,6 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.views.casting
import android.content.Context
import android.media.session.PlaybackState
import android.support.v4.media.session.PlaybackStateCompat
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
@ -13,7 +9,11 @@ import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.OptIn
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.futo.platformplayer.R
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.toHumanTime
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.Dispatchers
import kotlinx.coroutines.Job
@ -54,6 +51,7 @@ class CastView : ConstraintLayout {
val onMinimizeClick = Event0();
val onSettingsClick = Event0();
@OptIn(UnstableApi::class)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
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);
_timeBar.addListener(object : OnScrubListener {
_timeBar.addListener(object : TimeBar.OnScrubListener {
override fun onScrubStart(timeBar: TimeBar, position: Long) {
StateCasting.instance.videoSeekTo(position.toDouble());
}
@ -149,11 +147,9 @@ class CastView : ConstraintLayout {
_buttonPlay.visibility = View.VISIBLE;
}
val position = StateCasting.instance.activeDevice?.expectedCurrentTime?.times(1000.0)?.toLong();
if(StatePlayer.instance.hasMediaSession()) {
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) {
Glide.with(_thumbnail)
.load(video.thumbnails.getHQThumbnail())
@ -194,10 +191,11 @@ class CastView : ConstraintLayout {
_timeBar.setDuration(video.duration);
}
@OptIn(UnstableApi::class)
fun setTime(ms: Long) {
_textPosition.text = ms.toHumanTime(true);
_timeBar.setPosition(ms / 1000);
StatePlayer.instance.updateMediaSessionPlaybackState(getPlaybackStateCompat(), ms);
StatePlayer.instance.updateMediaSessionPlaybackState();
}
fun cleanup() {
@ -207,13 +205,4 @@ class CastView : ConstraintLayout {
_updateTimeJob = null;
_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;
}
}
}

View file

@ -1,5 +1,3 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.views.video
import android.content.Context
@ -9,6 +7,10 @@ import android.view.View
import android.widget.ImageButton
import android.widget.LinearLayout
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.Settings
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.toHumanTime
import com.futo.platformplayer.video.PlayerManager
import com.google.android.exoplayer2.ui.PlayerControlView
import com.google.android.exoplayer2.ui.StyledPlayerView
class FutoThumbnailPlayer : FutoVideoPlayerBase {
@ -28,7 +28,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
}
//Views
private val videoView : StyledPlayerView;
private val videoView : PlayerView;
private val videoControls : PlayerControlView;
private val buttonMute : ImageButton;
private val buttonUnMute : ImageButton;
@ -41,7 +41,8 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
private val _evMuteChanged = mutableListOf<(FutoThumbnailPlayer, Boolean)->Unit>();
constructor(context : Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
@OptIn(UnstableApi::class)
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
LayoutInflater.from(context).inflate(R.layout.thumbnail_video_view, this, true);
videoView = findViewById(R.id.video_player);
@ -70,7 +71,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
}
}
fun setLive(live : Boolean) {
fun setLive(live: Boolean) {
if(live) {
containerDuration.visibility = GONE;
containerLive.visibility = VISIBLE;
@ -81,7 +82,8 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
}
}
fun setPlayer(player : PlayerManager?){
@OptIn(UnstableApi::class)
fun setPlayer(player: PlayerManager?){
changePlayer(player);
player?.attach(videoView, PLAYER_STATE_NAME);
videoControls.player = player?.player;

View file

@ -1,5 +1,3 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.views.video
import android.content.Context
@ -15,6 +13,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.TextView
import androidx.annotation.OptIn
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.setMargins
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.StatePlayer
import com.futo.platformplayer.views.behavior.GestureControlView
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.PlayerControlView
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.ui.TimeBar
import com.google.android.exoplayer2.video.VideoSize
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerControlView
import androidx.media3.ui.PlayerView
import androidx.media3.ui.TimeBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -58,7 +58,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
//Views
private val _root: ConstraintLayout;
private val _videoView: StyledPlayerView;
private val _videoView: PlayerView;
val videoControls: PlayerControlView;
private val _videoControls_fullscreen: PlayerControlView;
@ -127,6 +127,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
val onVideoClicked = Event0();
val onTimeBarChanged = Event2<Long, Long>();
@OptIn(UnstableApi::class)
constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) {
LayoutInflater.from(context).inflate(R.layout.video_view, this, true);
_root = findViewById(R.id.videoview_root);
@ -139,8 +140,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
_control_rotate_lock = videoControls.findViewById(R.id.exo_rotate_lock);
_control_loop = videoControls.findViewById(R.id.exo_loop);
_control_cast = videoControls.findViewById(R.id.exo_cast);
_control_play = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play);
_time_bar = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
_control_play = videoControls.findViewById(androidx.media3.ui.R.id.exo_play);
_time_bar = videoControls.findViewById(androidx.media3.ui.R.id.exo_progress);
_control_chapter = videoControls.findViewById(R.id.text_chapter_current);
_buttonNext = videoControls.findViewById(R.id.button_next);
_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_loop_fullscreen = videoControls.findViewById(R.id.exo_loop);
_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);
_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);
_buttonNext_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_next);
@ -404,14 +405,15 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
return false;
}
@OptIn(UnstableApi::class)
fun setArtwork(drawable: Drawable?) {
if (drawable != null) {
_videoView.defaultArtwork = drawable;
_videoView.artworkDisplayMode = StyledPlayerView.ARTWORK_DISPLAY_MODE_FILL;
_videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_FILL;
fitOrFill(isFullScreen);
} else {
_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;
}
@OptIn(UnstableApi::class)
fun setFullScreen(fullScreen: Boolean) {
if (isFullScreen == fullScreen) {
return;
@ -538,6 +541,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
}
//Sizing
@OptIn(UnstableApi::class)
fun fitHeight(videoSize : VideoSize? = null){
Logger.i(TAG, "Video Fit Height");
if(_originalBottomMargin != 0) {

View file

@ -1,11 +1,27 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.views.video
import android.content.Context
import android.net.Uri
import android.util.AttributeSet
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.api.media.models.chapters.IChapter
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.states.StateApp
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.Dispatchers
import kotlinx.coroutines.launch
@ -251,6 +251,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
_targetTrackAudioBitrate = bitrate;
updateTrackSelector();
}
@OptIn(UnstableApi::class)
private fun updateTrackSelector() {
var builder = DefaultTrackSelector.Parameters.Builder(context);
if(_targetTrackVideoHeight > 0) {
@ -298,6 +299,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
return loadSelectedSources(play, resume);
}
@OptIn(UnstableApi::class)
fun swapSubtitles(scope: CoroutineScope, subtitles: ISubtitleSource?) {
if(subtitles == null)
clearSubtitles();
@ -369,6 +371,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
}
//Video loads
@OptIn(UnstableApi::class)
private fun swapVideoSourceLocal(videoSource: LocalVideoSource) {
Logger.i(TAG, "Loading VideoSource [Local]");
val file = File(videoSource.filePath);
@ -377,14 +380,13 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
}
@OptIn(UnstableApi::class)
private fun swapVideoSourceUrlRange(videoSource: JSVideoUrlRangeSource) {
Logger.i(TAG, "Loading JSVideoUrlRangeSource");
if(videoSource.hasItag) {
//Temporary workaround for Youtube
try {
_lastVideoMediaSource = VideoHelper.convertItagSourceToChunkedDashSource(videoSource);
if(_lastVideoMediaSource == null)
throw java.lang.IllegalStateException("Dash manifest workaround failed");
return;
}
//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...");
}
@OptIn(UnstableApi::class)
private fun swapVideoSourceUrl(videoSource: IVideoUrlSource) {
Logger.i(TAG, "Loading VideoSource [Url]");
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory()
.setUserAgent(DEFAULT_USER_AGENT))
.createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl()));
}
@OptIn(UnstableApi::class)
private fun swapVideoSourceDash(videoSource: IDashManifestSource) {
Logger.i(TAG, "Loading VideoSource [Dash]");
_lastVideoMediaSource = DashMediaSource.Factory(DefaultHttpDataSource.Factory()
.setUserAgent(DEFAULT_USER_AGENT))
.createMediaSource(MediaItem.fromUri(videoSource.url))
}
@OptIn(UnstableApi::class)
private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) {
Logger.i(TAG, "Loading VideoSource [HLS]");
_lastVideoMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()
@ -416,7 +421,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
.createMediaSource(MediaItem.fromUri(videoSource.url));
}
//Audio loads
@OptIn(UnstableApi::class)
private fun swapAudioSourceLocal(audioSource: LocalAudioSource) {
Logger.i(TAG, "Loading AudioSource [Local]");
val file = File(audioSource.filePath);
@ -425,6 +432,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
}
@OptIn(UnstableApi::class)
private fun swapAudioSourceUrlRange(audioSource: JSAudioUrlRangeSource) {
Logger.i(TAG, "Loading JSAudioUrlRangeSource");
if(audioSource.hasItag) {
@ -444,12 +452,14 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
}
else throw IllegalArgumentException("source without itag data...")
}
@OptIn(UnstableApi::class)
private fun swapAudioSourceUrl(audioSource: IAudioUrlSource) {
Logger.i(TAG, "Loading AudioSource [Url]");
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory()
.setUserAgent(DEFAULT_USER_AGENT))
.createMediaSource(MediaItem.fromUri(audioSource.getAudioUrl()));
}
@OptIn(UnstableApi::class)
private fun swapAudioSourceHLS(audioSource: IHLSManifestAudioSource) {
Logger.i(TAG, "Loading AudioSource [HLS]");
_lastAudioMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()
@ -479,6 +489,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
return VideoHelper.selectBestAudioSource(video.video, PREFERED_AUDIO_CONTAINERS, preferredLanguage);
}
@OptIn(UnstableApi::class)
private fun loadSelectedSources(play: Boolean, resume: Boolean): Boolean {
val sourceVideo = if(!isAudioMode || _lastAudioMediaSource == null) _lastVideoMediaSource else null;
val sourceAudio = _lastAudioMediaSource;
@ -506,11 +517,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
return true;
}
@OptIn(UnstableApi::class)
private fun reloadMediaSource(play: Boolean = false, resume: Boolean = true) {
val player = exoPlayer
if (player == null)
return;
val player = exoPlayer ?: return
val positionBefore = player.player.currentPosition;
if(_mediaSource != null) {
player.player.setMediaSource(_mediaSource!!);

View file

@ -1,26 +1,28 @@
package com.futo.platformplayer.views.video.datasources;
import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.datasource.HttpUtil.buildRangeRequestHeader;
import static java.lang.Math.min;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpUtil;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import androidx.media3.common.C;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.BaseDataSource;
import androidx.media3.datasource.DataSourceException;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.HttpUtil;
import androidx.media3.datasource.TransferListener;
import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableMap;
@ -45,6 +47,7 @@ import java.util.zip.GZIPInputStream;
* Based on the default ExoPlayer DefaultHttpDataSource
*/
@UnstableApi
public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
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
* {@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}.
*
@ -160,7 +163,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
*
* <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.
* @return This factory.
@ -367,12 +370,12 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
if (dataSpec.length != C.LENGTH_UNSET) {
bytesToRead = dataSpec.length;
} else {
long contentLength =
HttpUtil.getContentLength(
connection.getHeaderField(HttpHeaders.CONTENT_LENGTH),
connection.getHeaderField(HttpHeaders.CONTENT_RANGE));
bytesToRead =
contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET;
long contentLength = HttpUtil.getContentLength(
connection.getHeaderField(HttpHeaders.CONTENT_LENGTH),
connection.getHeaderField(HttpHeaders.CONTENT_RANGE)
);
bytesToRead = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET;
}
} else {
// 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. */
private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
URL url = new URL(dataSpec.uri.toString());
@HttpMethod int httpMethod = dataSpec.httpMethod;
@DataSpec.HttpMethod int httpMethod = dataSpec.httpMethod;
@Nullable byte[] httpBody = dataSpec.httpBody;
long position = dataSpec.position;
long length = dataSpec.length;
@ -543,7 +546,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
*/
private HttpURLConnection makeConnection(
URL url,
@HttpMethod int httpMethod,
@DataSpec.HttpMethod int httpMethod,
@Nullable byte[] httpBody,
long position,
long length,

View file

@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:background="@color/transparent"
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:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -6,7 +6,7 @@
android:id="@+id/videoview_root"
android:background="@color/transparent"
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:layout_width="match_parent"
android:layout_height="match_parent"