From 67b5ff8da3472d40ce797cc8bf5515e1b76d7048 Mon Sep 17 00:00:00 2001 From: Jakub Date: Sun, 24 Apr 2022 17:58:49 +0200 Subject: [PATCH] Feature/march 2022 changes (#980) * Fixed android embedding v2 * ExoPlayer updated to latest version * Fixed ExoPlayer deprecated methods * Updated android native code * Updated android native code * Updated dependencies * 0.0.82 release * Fixed lint issue * Disabled test from CI --- .github/workflows/ci.yml | 21 +- CHANGELOG.md | 5 + android/build.gradle | 38 +-- android/gradle.properties | 4 - .../gradle/wrapper/gradle-wrapper.properties | 3 +- .../jhomlala/better_player/BetterPlayer.kt | 310 ++++++++---------- .../better_player/BetterPlayerPlugin.kt | 24 +- .../better_player/CacheDataSourceFactory.kt | 12 +- .../com/jhomlala/better_player/CacheWorker.kt | 20 +- .../jhomlala/better_player/DataSourceUtils.kt | 7 +- .../com/jhomlala/better_player/ImageWorker.kt | 7 +- docs/install.md | 2 +- .../android/app/src/main/AndroidManifest.xml | 1 - example/pubspec.yaml | 5 +- pubspec.lock | 15 +- pubspec.yaml | 10 +- test/mock_method_channel.dart | 1 + 17 files changed, 215 insertions(+), 270 deletions(-) delete mode 100644 android/gradle.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a704449e2..4fab42a20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,13 @@ jobs: - name: Lint using flutter analyze run: flutter analyze - - #test: - # name: Test - # runs-on: ubuntu-latest - # container: cirrusci/flutter:stable - - # steps: - # - name: Checkout code - # uses: actions/checkout@v2 - # - name: Test using flutter test - # run: flutter test +# test: +# name: Test +# runs-on: ubuntu-latest +# container: cirrusci/flutter:stable +# +# steps: +# - name: Checkout code +# uses: actions/checkout@v2 +# - name: Test using flutter test +# run: flutter test diff --git a/CHANGELOG.md b/CHANGELOG.md index e877ccd58..c2cf3a80b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.0.82 +* Updated ExoPlayer version to 2.17.1. +* Updated dependencies. +* Android native code refactor. + ## 0.0.81 * Fixed full screen button padding in material controls. * Added `setBetterPlayerControlsConfiguration` in `BetterPlayerController`. diff --git a/android/build.gradle b/android/build.gradle index dddb0a3be..79f606b82 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,9 +1,8 @@ -group 'com.jhomlala.better_player' -version '0.0.77' +group 'com.jhomlala.better_player.better_player' +version '1.0-SNAPSHOT' buildscript { - - ext.exoPlayerVersion = "2.15.1" + ext.exoPlayerVersion = "2.17.1" ext.lifecycleVersion = "2.4.0-beta01" ext.annotationVersion = "1.2.0" ext.workVersion = "2.7.0" @@ -17,7 +16,8 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:$gradleVersion" + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } @@ -34,18 +34,21 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 31 - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - lintOptions { - disable 'InvalidPackage' + + kotlinOptions { + jvmTarget = '1.8' } - android { - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 16 } dependencies { @@ -62,10 +65,7 @@ android { implementation "androidx.work:work-runtime:$workVersion" } } + dependencies { - implementation "androidx.core:core-ktx:$coreVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" } -repositories { - mavenCentral() -} diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index a5965ab8d..000000000 --- a/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 9f96ce648..da9702f9e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Oct 17 09:04:56 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayer.kt b/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayer.kt index e1f283185..f5e494430 100644 --- a/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayer.kt +++ b/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayer.kt @@ -58,7 +58,9 @@ import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.drm.DrmSessionManagerProvider import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides import com.google.android.exoplayer2.upstream.DataSource +import com.google.android.exoplayer2.upstream.DefaultDataSource import com.google.android.exoplayer2.util.Util import java.io.File import java.lang.Exception @@ -74,7 +76,7 @@ internal class BetterPlayer( customDefaultLoadControl: CustomDefaultLoadControl?, result: MethodChannel.Result ) { - private val exoPlayer: SimpleExoPlayer? + private val exoPlayer: ExoPlayer? private val eventSink = QueuingEventSink() private val trackSelector: DefaultTrackSelector = DefaultTrackSelector(context) private val loadControl: LoadControl @@ -103,7 +105,7 @@ internal class BetterPlayer( this.customDefaultLoadControl.bufferForPlaybackAfterRebufferMs ) loadControl = loadBuilder.build() - exoPlayer = SimpleExoPlayer.Builder(context) + exoPlayer = ExoPlayer.Builder(context) .setTrackSelector(trackSelector) .setLoadControl(loadControl) .build() @@ -189,16 +191,16 @@ internal class BetterPlayer( ) } } else { - dataSourceFactory = DefaultDataSourceFactory(context, userAgent) + dataSourceFactory = DefaultDataSource.Factory(context) } val mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, cacheKey, context) if (overriddenDuration != 0L) { val clippingMediaSource = ClippingMediaSource(mediaSource, 0, overriddenDuration * 1000) - exoPlayer!!.setMediaSource(clippingMediaSource) + exoPlayer?.setMediaSource(clippingMediaSource) } else { - exoPlayer!!.setMediaSource(mediaSource) + exoPlayer?.setMediaSource(mediaSource) } - exoPlayer.prepare() + exoPlayer?.prepare() result.success(null) } @@ -263,7 +265,9 @@ internal class BetterPlayer( //Bitmap here is already processed and it's very small, so it won't //break anything. bitmap = BitmapFactory.decodeFile(filePath) - callback.onBitmap(bitmap!!) + bitmap?.let { bitmap -> + callback.onBitmap(bitmap) + } } if (state == WorkInfo.State.SUCCEEDED || state == WorkInfo.State.CANCELLED || state == WorkInfo.State.FAILED) { val uuid = imageWorkRequest.id @@ -301,19 +305,26 @@ internal class BetterPlayer( playerNotificationChannelName = DEFAULT_NOTIFICATION_CHANNEL } } + playerNotificationManager = PlayerNotificationManager.Builder( - context, - NOTIFICATION_ID, - playerNotificationChannelName!!, - mediaDescriptionAdapter - ).build() - playerNotificationManager!!.setPlayer(exoPlayer) - playerNotificationManager!!.setUseNextAction(false) - playerNotificationManager!!.setUsePreviousAction(false) - playerNotificationManager!!.setUseStopAction(false) - val mediaSession = setupMediaSession(context, false) - playerNotificationManager!!.setMediaSessionToken(mediaSession.sessionToken) - playerNotificationManager!!.setControlDispatcher(setupControlDispatcher()) + context, NOTIFICATION_ID, + playerNotificationChannelName!! + ).setMediaDescriptionAdapter(mediaDescriptionAdapter).build() + + playerNotificationManager?.apply { + + exoPlayer?.let { + setPlayer(ForwardingPlayer(exoPlayer)) + setUseNextAction(false) + setUsePreviousAction(false) + setUseStopAction(false) + } + + setupMediaSession(context)?.let { + setMediaSessionToken(it.sessionToken) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { refreshHandler = Handler(Looper.getMainLooper()) refreshRunnable = Runnable { @@ -328,109 +339,37 @@ internal class BetterPlayer( .setState(PlaybackStateCompat.STATE_PAUSED, position, 1.0f) .build() } - mediaSession.setPlaybackState(playbackState) - refreshHandler!!.postDelayed(refreshRunnable!!, 1000) + mediaSession?.setPlaybackState(playbackState) + refreshHandler?.postDelayed(refreshRunnable!!, 1000) } - refreshHandler!!.postDelayed(refreshRunnable!!, 0) + refreshHandler?.postDelayed(refreshRunnable!!, 0) } exoPlayerEventListener = object : Player.Listener { override fun onPlaybackStateChanged(playbackState: Int) { - mediaSession.setMetadata( + mediaSession?.setMetadata( MediaMetadataCompat.Builder() .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, getDuration()) .build() ) } } - exoPlayer!!.addListener(exoPlayerEventListener!!) - exoPlayer.seekTo(0) - } - - private fun setupControlDispatcher(): ControlDispatcher { - return object : ControlDispatcher { - override fun dispatchPrepare(player: Player): Boolean { - return false - } - - override fun dispatchSetPlayWhenReady(player: Player, playWhenReady: Boolean): Boolean { - if (player.playWhenReady) { - sendEvent("pause") - } else { - sendEvent("play") - } - return true - } - - override fun dispatchSeekTo( - player: Player, - windowIndex: Int, - positionMs: Long - ): Boolean { - sendSeekToEvent(positionMs) - return true - } - - override fun dispatchPrevious(player: Player): Boolean { - return false - } - - override fun dispatchNext(player: Player): Boolean { - return false - } - - override fun dispatchRewind(player: Player): Boolean { - sendSeekToEvent(player.currentPosition - 5000) - return false - } - - override fun dispatchFastForward(player: Player): Boolean { - sendSeekToEvent(player.currentPosition + 5000) - return true - } - - override fun dispatchSetRepeatMode(player: Player, repeatMode: Int): Boolean { - return false - } - - override fun dispatchSetShuffleModeEnabled( - player: Player, - shuffleModeEnabled: Boolean - ): Boolean { - return false - } - - override fun dispatchStop(player: Player, reset: Boolean): Boolean { - return false - } - - override fun dispatchSetPlaybackParameters( - player: Player, - playbackParameters: PlaybackParameters - ): Boolean { - return false - } - - override fun isRewindEnabled(): Boolean { - return true - } - - override fun isFastForwardEnabled(): Boolean { - return true - } + exoPlayerEventListener?.let { exoPlayerEventListener -> + exoPlayer?.addListener(exoPlayerEventListener) } + exoPlayer?.seekTo(0) } fun disposeRemoteNotifications() { - if (exoPlayerEventListener != null) { - exoPlayer!!.removeListener(exoPlayerEventListener!!) + exoPlayerEventListener?.let { exoPlayerEventListener -> + exoPlayer?.removeListener(exoPlayerEventListener) } if (refreshHandler != null) { - refreshHandler!!.removeCallbacksAndMessages(null) + refreshHandler?.removeCallbacksAndMessages(null) refreshHandler = null refreshRunnable = null } if (playerNotificationManager != null) { - playerNotificationManager!!.setPlayer(null) + playerNotificationManager?.setPlayer(null) } bitmap = null } @@ -465,19 +404,19 @@ internal class BetterPlayer( } val mediaItem = mediaItemBuilder.build() var drmSessionManagerProvider: DrmSessionManagerProvider? = null - if (drmSessionManager != null) { - drmSessionManagerProvider = DrmSessionManagerProvider { drmSessionManager!! } + drmSessionManager?.let { drmSessionManager -> + drmSessionManagerProvider = DrmSessionManagerProvider { drmSessionManager } } return when (type) { C.TYPE_SS -> SsMediaSource.Factory( DefaultSsChunkSource.Factory(mediaDataSourceFactory), - DefaultDataSourceFactory(context, null, mediaDataSourceFactory) + DefaultDataSource.Factory(context, mediaDataSourceFactory) ) .setDrmSessionManagerProvider(drmSessionManagerProvider) .createMediaSource(mediaItem) C.TYPE_DASH -> DashMediaSource.Factory( DefaultDashChunkSource.Factory(mediaDataSourceFactory), - DefaultDataSourceFactory(context, null, mediaDataSourceFactory) + DefaultDataSource.Factory(context, mediaDataSourceFactory) ) .setDrmSessionManagerProvider(drmSessionManagerProvider) .createMediaSource(mediaItem) @@ -510,9 +449,9 @@ internal class BetterPlayer( } }) surface = Surface(textureEntry.surfaceTexture()) - exoPlayer!!.setVideoSurface(surface) + exoPlayer?.setVideoSurface(surface) setAudioAttributes(exoPlayer, true) - exoPlayer.addListener(object : Player.Listener { + exoPlayer?.addListener(object : Player.Listener { override fun onPlaybackStateChanged(playbackState: Int) { when (playbackState) { Player.STATE_BUFFERING -> { @@ -552,7 +491,7 @@ internal class BetterPlayer( } fun sendBufferingUpdate(isFromBufferingStart: Boolean) { - val bufferedPosition = exoPlayer!!.bufferedPosition + val bufferedPosition = exoPlayer?.bufferedPosition ?: 0L if (isFromBufferingStart || bufferedPosition != lastSendBufferedPosition) { val event: MutableMap = HashMap() event["event"] = "bufferingUpdate" @@ -564,8 +503,9 @@ internal class BetterPlayer( } } - private fun setAudioAttributes(exoPlayer: SimpleExoPlayer?, mixWithOthers: Boolean) { - val audioComponent = exoPlayer!!.audioComponent ?: return + @Suppress("DEPRECATION") + private fun setAudioAttributes(exoPlayer: ExoPlayer?, mixWithOthers: Boolean) { + val audioComponent = exoPlayer?.audioComponent ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { audioComponent.setAudioAttributes( AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MOVIE).build(), @@ -580,27 +520,27 @@ internal class BetterPlayer( } fun play() { - exoPlayer!!.playWhenReady = true + exoPlayer?.playWhenReady = true } fun pause() { - exoPlayer!!.playWhenReady = false + exoPlayer?.playWhenReady = false } fun setLooping(value: Boolean) { - exoPlayer!!.repeatMode = if (value) Player.REPEAT_MODE_ALL else Player.REPEAT_MODE_OFF + exoPlayer?.repeatMode = if (value) Player.REPEAT_MODE_ALL else Player.REPEAT_MODE_OFF } fun setVolume(value: Double) { val bracketedValue = max(0.0, min(1.0, value)) .toFloat() - exoPlayer!!.volume = bracketedValue + exoPlayer?.volume = bracketedValue } fun setSpeed(value: Double) { val bracketedValue = value.toFloat() val playbackParameters = PlaybackParameters(bracketedValue) - exoPlayer!!.playbackParameters = playbackParameters + exoPlayer?.playbackParameters = playbackParameters } fun setTrackParameters(width: Int, height: Int, bitrate: Int) { @@ -619,20 +559,24 @@ internal class BetterPlayer( } fun seekTo(location: Int) { - exoPlayer!!.seekTo(location.toLong()) + exoPlayer?.seekTo(location.toLong()) } val position: Long - get() = exoPlayer!!.currentPosition + get() = exoPlayer?.currentPosition ?: 0L + val absolutePosition: Long get() { - val timeline = exoPlayer!!.currentTimeline - if (!timeline.isEmpty) { - val windowStartTimeMs = timeline.getWindow(0, Timeline.Window()).windowStartTimeMs - val pos = exoPlayer.currentPosition - return windowStartTimeMs + pos + val timeline = exoPlayer?.currentTimeline + timeline?.let { + if (!timeline.isEmpty) { + val windowStartTimeMs = + timeline.getWindow(0, Timeline.Window()).windowStartTimeMs + val pos = exoPlayer?.currentPosition ?: 0L + return windowStartTimeMs + pos + } } - return exoPlayer.currentPosition + return exoPlayer?.currentPosition ?: 0L } private fun sendInitialized() { @@ -641,15 +585,15 @@ internal class BetterPlayer( event["event"] = "initialized" event["key"] = key event["duration"] = getDuration() - if (exoPlayer!!.videoFormat != null) { + if (exoPlayer?.videoFormat != null) { val videoFormat = exoPlayer.videoFormat - var width = videoFormat!!.width - var height = videoFormat.height - val rotationDegrees = videoFormat.rotationDegrees + var width = videoFormat?.width + var height = videoFormat?.height + val rotationDegrees = videoFormat?.rotationDegrees // Switch the width/height if video was taken in portrait mode if (rotationDegrees == 90 || rotationDegrees == 270) { - width = exoPlayer.videoFormat!!.height - height = exoPlayer.videoFormat!!.width + width = exoPlayer.videoFormat?.height + height = exoPlayer.videoFormat?.width } event["width"] = width event["height"] = height @@ -658,39 +602,40 @@ internal class BetterPlayer( } } - private fun getDuration(): Long = exoPlayer!!.duration + private fun getDuration(): Long = exoPlayer?.duration ?: 0L /** * Create media session which will be used in notifications, pip mode. * * @param context - android context - * @param setupControlDispatcher - should add control dispatcher to created MediaSession * @return - configured MediaSession instance */ - fun setupMediaSession(context: Context?, setupControlDispatcher: Boolean): MediaSessionCompat { + @SuppressLint("InlinedApi") + fun setupMediaSession(context: Context?): MediaSessionCompat? { mediaSession?.release() - val mediaButtonReceiver = ComponentName(context!!, MediaButtonReceiver::class.java) - val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON) - val pendingIntent = PendingIntent.getBroadcast( - context!!, - 0, mediaButtonIntent, - PendingIntent.FLAG_IMMUTABLE - ) - val mediaSession = MediaSessionCompat(context!!, TAG, null, pendingIntent) - mediaSession.setCallback(object : MediaSessionCompat.Callback() { - override fun onSeekTo(pos: Long) { - sendSeekToEvent(pos) - super.onSeekTo(pos) - } - }) - mediaSession.isActive = true - val mediaSessionConnector = MediaSessionConnector(mediaSession) - if (setupControlDispatcher) { - mediaSessionConnector.setControlDispatcher(setupControlDispatcher()) - } - mediaSessionConnector.setPlayer(exoPlayer) - this.mediaSession = mediaSession - return mediaSession + context?.let { + + val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON) + val pendingIntent = PendingIntent.getBroadcast( + context, + 0, mediaButtonIntent, + PendingIntent.FLAG_IMMUTABLE + ) + val mediaSession = MediaSessionCompat(context, TAG, null, pendingIntent) + mediaSession.setCallback(object : MediaSessionCompat.Callback() { + override fun onSeekTo(pos: Long) { + sendSeekToEvent(pos) + super.onSeekTo(pos) + } + }) + mediaSession.isActive = true + val mediaSessionConnector = MediaSessionConnector(mediaSession) + mediaSessionConnector.setPlayer(exoPlayer) + this.mediaSession = mediaSession + return mediaSession + } + return null + } fun onPictureInPictureStatusChanged(inPip: Boolean) { @@ -701,17 +646,11 @@ internal class BetterPlayer( fun disposeMediaSession() { if (mediaSession != null) { - mediaSession!!.release() + mediaSession?.release() } mediaSession = null } - private fun sendEvent(eventType: String) { - val event: MutableMap = HashMap() - event["event"] = eventType - eventSink.success(event) - } - fun setAudioTrack(name: String, index: Int) { try { val mappedTrackInfo = trackSelector.currentMappedTrackInfo @@ -767,20 +706,23 @@ internal class BetterPlayer( val mappedTrackInfo = trackSelector.currentMappedTrackInfo if (mappedTrackInfo != null) { val builder = trackSelector.parameters.buildUpon() - builder.clearSelectionOverrides(rendererIndex) .setRendererDisabled(rendererIndex, false) - val tracks = intArrayOf(groupElementIndex) - val override = SelectionOverride(groupIndex, *tracks) - builder.setSelectionOverride( - rendererIndex, - mappedTrackInfo.getTrackGroups(rendererIndex), override - ) + .setTrackSelectionOverrides( + TrackSelectionOverrides.Builder().addOverride( + TrackSelectionOverrides.TrackSelectionOverride( + mappedTrackInfo.getTrackGroups( + rendererIndex + ).get(groupIndex) + ) + ).build() + ) + trackSelector.setParameters(builder) } } private fun sendSeekToEvent(positionMs: Long) { - exoPlayer!!.seekTo(positionMs) + exoPlayer?.seekTo(positionMs) val event: MutableMap = HashMap() event["event"] = "seek" event["position"] = positionMs @@ -795,13 +737,11 @@ internal class BetterPlayer( disposeMediaSession() disposeRemoteNotifications() if (isInitialized) { - exoPlayer!!.stop() + exoPlayer?.stop() } textureEntry.release() eventChannel.setStreamHandler(null) - if (surface != null) { - surface!!.release() - } + surface?.release() exoPlayer?.release() } @@ -829,10 +769,12 @@ internal class BetterPlayer( private const val NOTIFICATION_ID = 20772077 //Clear cache without accessing BetterPlayerCache. - fun clearCache(context: Context, result: MethodChannel.Result) { + fun clearCache(context: Context?, result: MethodChannel.Result) { try { - val file = File(context.cacheDir, "betterPlayerCache") - deleteDirectory(file) + context?.let { context -> + val file = File(context.cacheDir, "betterPlayerCache") + deleteDirectory(file) + } result.success(null) } catch (exception: Exception) { Log.e(TAG, exception.toString()) @@ -874,17 +816,21 @@ internal class BetterPlayer( headers[headerKey] ) } - val cacheWorkRequest = OneTimeWorkRequest.Builder(CacheWorker::class.java) - .addTag(dataSource!!) - .setInputData(dataBuilder.build()).build() - WorkManager.getInstance(context!!).enqueue(cacheWorkRequest) + if (dataSource != null && context != null) { + val cacheWorkRequest = OneTimeWorkRequest.Builder(CacheWorker::class.java) + .addTag(dataSource) + .setInputData(dataBuilder.build()).build() + WorkManager.getInstance(context).enqueue(cacheWorkRequest) + } result.success(null) } //Stop pre cache of video with given url. If there's no work manager job for given url, then //it will be ignored. fun stopPreCache(context: Context?, url: String?, result: MethodChannel.Result) { - WorkManager.getInstance(context!!).cancelAllWorkByTag(url!!) + if (url != null && context != null) { + WorkManager.getInstance(context).cancelAllWorkByTag(url) + } result.success(null) } } diff --git a/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayerPlugin.kt b/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayerPlugin.kt index ced52802a..2dae96be4 100644 --- a/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayerPlugin.kt +++ b/android/src/main/kotlin/com/jhomlala/better_player/BetterPlayerPlugin.kt @@ -59,7 +59,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { }, binding.textureRegistry ) - flutterState!!.startListening(this) + flutterState?.startListening(this) } @@ -69,7 +69,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { } disposeAllPlayers() releaseCache() - flutterState!!.stopListening() + flutterState?.stopListening() flutterState = null } @@ -92,7 +92,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - if (flutterState == null || flutterState!!.textureRegistry == null) { + if (flutterState == null || flutterState?.textureRegistry == null) { result.error("no_activity", "better_player plugin requires a foreground activity", null) return } @@ -101,7 +101,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { CREATE_METHOD -> { val handle = flutterState!!.textureRegistry!!.createSurfaceTexture() val eventChannel = EventChannel( - flutterState!!.binaryMessenger, EVENTS_CHANNEL + handle.id() + flutterState?.binaryMessenger, EVENTS_CHANNEL + handle.id() ) var customDefaultLoadControl: CustomDefaultLoadControl? = null if (call.hasArgument(MIN_BUFFER_MS) && call.hasArgument(MAX_BUFFER_MS) && @@ -116,7 +116,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { ) } val player = BetterPlayer( - flutterState!!.applicationContext, eventChannel, handle, + flutterState?.applicationContext!!, eventChannel, handle, customDefaultLoadControl, result ) videoPlayers.put(handle.id(), player) @@ -247,7 +247,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { flutterState!!.keyForAsset[asset] } player.setDataSource( - flutterState!!.applicationContext, + flutterState?.applicationContext!!, key, "asset:///$assetLookupKey", null, @@ -316,7 +316,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { val headers: Map = getParameter(dataSource, HEADERS_PARAMETER, HashMap()) BetterPlayer.preCache( - flutterState!!.applicationContext, + flutterState?.applicationContext, uri, preCacheSize, maxCacheSize, @@ -336,11 +336,11 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { */ private fun stopPreCache(call: MethodCall, result: MethodChannel.Result) { val url = call.argument(URL_PARAMETER) - BetterPlayer.stopPreCache(flutterState!!.applicationContext, url, result) + BetterPlayer.stopPreCache(flutterState?.applicationContext, url, result) } private fun clearCache(result: MethodChannel.Result) { - BetterPlayer.clearCache(flutterState!!.applicationContext, result) + BetterPlayer.clearCache(flutterState?.applicationContext, result) } private fun getTextureId(betterPlayer: BetterPlayer): Long? { @@ -374,7 +374,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { val activityName = getParameter(dataSource, ACTIVITY_NAME_PARAMETER, "MainActivity") betterPlayer.setupPlayerNotification( - flutterState!!.applicationContext, + flutterState?.applicationContext!!, title, author, imageUrl, notificationChannelName, activityName ) } @@ -391,7 +391,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { } @Suppress("UNCHECKED_CAST") private fun getParameter(parameters: Map?, key: String, defaultValue: T): T { - if (parameters!!.containsKey(key)) { + if (parameters?.containsKey(key) == true) { val value = parameters[key] if (value != null) { return value as T @@ -408,7 +408,7 @@ class BetterPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { private fun enablePictureInPicture(player: BetterPlayer) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - player.setupMediaSession(flutterState!!.applicationContext, true) + player.setupMediaSession(flutterState!!.applicationContext) activity!!.enterPictureInPictureMode(PictureInPictureParams.Builder().build()) startPictureInPictureListenerTimer(player) player.onPictureInPictureStatusChanged(true) diff --git a/android/src/main/kotlin/com/jhomlala/better_player/CacheDataSourceFactory.kt b/android/src/main/kotlin/com/jhomlala/better_player/CacheDataSourceFactory.kt index a1e76c957..a75807869 100644 --- a/android/src/main/kotlin/com/jhomlala/better_player/CacheDataSourceFactory.kt +++ b/android/src/main/kotlin/com/jhomlala/better_player/CacheDataSourceFactory.kt @@ -2,11 +2,11 @@ package com.jhomlala.better_player import android.content.Context import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.cache.CacheDataSource import com.google.android.exoplayer2.upstream.FileDataSource import com.google.android.exoplayer2.upstream.cache.CacheDataSink import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter +import com.google.android.exoplayer2.upstream.DefaultDataSource internal class CacheDataSourceFactory( private val context: Context, @@ -14,14 +14,14 @@ internal class CacheDataSourceFactory( private val maxFileSize: Long, upstreamDataSource: DataSource.Factory? ) : DataSource.Factory { - private val defaultDatasourceFactory: DefaultDataSourceFactory + private var defaultDatasourceFactory: DefaultDataSource.Factory? = null override fun createDataSource(): CacheDataSource { val betterPlayerCache = BetterPlayerCache.createCache(context, maxCacheSize) ?: throw IllegalStateException("Cache can't be null.") return CacheDataSource( betterPlayerCache, - defaultDatasourceFactory.createDataSource(), + defaultDatasourceFactory?.createDataSource(), FileDataSource(), CacheDataSink(betterPlayerCache, maxFileSize), CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, @@ -31,7 +31,9 @@ internal class CacheDataSourceFactory( init { val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build() - defaultDatasourceFactory = - DefaultDataSourceFactory(context, bandwidthMeter, upstreamDataSource!!) + upstreamDataSource?.let { + defaultDatasourceFactory = DefaultDataSource.Factory(context, upstreamDataSource) + defaultDatasourceFactory?.setTransferListener(bandwidthMeter) + } } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/jhomlala/better_player/CacheWorker.kt b/android/src/main/kotlin/com/jhomlala/better_player/CacheWorker.kt index e1f376c5a..e47d4e1ff 100644 --- a/android/src/main/kotlin/com/jhomlala/better_player/CacheWorker.kt +++ b/android/src/main/kotlin/com/jhomlala/better_player/CacheWorker.kt @@ -19,11 +19,11 @@ import java.util.* * will be executed in work manager. */ class CacheWorker( - private val mContext: Context, + private val context: Context, params: WorkerParameters -) : Worker(mContext, params) { - private var mCacheWriter: CacheWriter? = null - private var mLastCacheReportIndex = 0 +) : Worker(context, params) { + private var cacheWriter: CacheWriter? = null + private var lastCacheReportIndex = 0 override fun doWork(): Result { try { val data = inputData @@ -49,26 +49,26 @@ class CacheWorker( dataSpec = dataSpec.buildUpon().setKey(cacheKey).build() } val cacheDataSourceFactory = CacheDataSourceFactory( - mContext, + context, maxCacheSize, maxCacheFileSize, dataSourceFactory ) - mCacheWriter = CacheWriter( + cacheWriter = CacheWriter( cacheDataSourceFactory.createDataSource(), dataSpec, null ) { _: Long, bytesCached: Long, _: Long -> val completedData = (bytesCached * 100f / preCacheSize).toDouble() - if (completedData >= mLastCacheReportIndex * 10) { - mLastCacheReportIndex += 1 + if (completedData >= lastCacheReportIndex * 10) { + lastCacheReportIndex += 1 Log.d( TAG, "Completed pre cache of " + url + ": " + completedData.toInt() + "%" ) } } - mCacheWriter!!.cache() + cacheWriter?.cache() } else { Log.e(TAG, "Preloading only possible for remote data sources") return Result.failure() @@ -86,7 +86,7 @@ class CacheWorker( override fun onStopped() { try { - mCacheWriter!!.cancel() + cacheWriter?.cancel() super.onStopped() } catch (exception: Exception) { Log.e(TAG, exception.toString()) diff --git a/android/src/main/kotlin/com/jhomlala/better_player/DataSourceUtils.kt b/android/src/main/kotlin/com/jhomlala/better_player/DataSourceUtils.kt index d19c0e6f1..bb691b443 100644 --- a/android/src/main/kotlin/com/jhomlala/better_player/DataSourceUtils.kt +++ b/android/src/main/kotlin/com/jhomlala/better_player/DataSourceUtils.kt @@ -2,7 +2,6 @@ package com.jhomlala.better_player import android.net.Uri import com.google.android.exoplayer2.upstream.DataSource -import com.jhomlala.better_player.DataSourceUtils import com.google.android.exoplayer2.upstream.DefaultHttpDataSource internal object DataSourceUtils { @@ -10,7 +9,7 @@ internal object DataSourceUtils { private const val USER_AGENT_PROPERTY = "http.agent" @JvmStatic - fun getUserAgent(headers: Map?): String { + fun getUserAgent(headers: Map?): String? { var userAgent = System.getProperty(USER_AGENT_PROPERTY) if (headers != null && headers.containsKey(USER_AGENT)) { val userAgentHeader = headers[USER_AGENT] @@ -34,9 +33,7 @@ internal object DataSourceUtils { if (headers != null) { val notNullHeaders = mutableMapOf() headers.forEach { entry -> - if (entry.key != null && entry.value != null) { - notNullHeaders[entry.key!!] = entry.value!! - } + notNullHeaders[entry.key] = entry.value } (dataSourceFactory as DefaultHttpDataSource.Factory).setDefaultRequestProperties( notNullHeaders diff --git a/android/src/main/kotlin/com/jhomlala/better_player/ImageWorker.kt b/android/src/main/kotlin/com/jhomlala/better_player/ImageWorker.kt index 1a7bea3c4..fa4bcf494 100644 --- a/android/src/main/kotlin/com/jhomlala/better_player/ImageWorker.kt +++ b/android/src/main/kotlin/com/jhomlala/better_player/ImageWorker.kt @@ -7,11 +7,7 @@ import android.net.Uri import android.util.Log import androidx.work.Data import androidx.work.WorkerParameters -import androidx.work.ListenableWorker import androidx.work.Worker -import com.jhomlala.better_player.BetterPlayerPlugin -import com.jhomlala.better_player.DataSourceUtils -import com.jhomlala.better_player.ImageWorker import java.io.FileOutputStream import java.io.InputStream import java.lang.Exception @@ -26,8 +22,7 @@ class ImageWorker( return try { val imageUrl = inputData.getString(BetterPlayerPlugin.URL_PARAMETER) ?: return Result.failure() - var bitmap: Bitmap? = null - bitmap = if (DataSourceUtils.isHTTP(Uri.parse(imageUrl))) { + val bitmap: Bitmap? = if (DataSourceUtils.isHTTP(Uri.parse(imageUrl))) { getBitmapFromExternalURL(imageUrl) } else { getBitmapFromInternalURL(imageUrl) diff --git a/docs/install.md b/docs/install.md index 63c582367..b0d086ca9 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,7 +4,7 @@ ```yaml dependencies: - better_player: ^0.0.81 + better_player: ^0.0.82 ``` 2. Install it diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 9bcf886a4..0765fd737 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.jhomlala.better_player_example"> =2.12.0 <3.0.0' dependencies: - #better_player: any better_player: path: ../ flutter: sdk: flutter flutter_localizations: sdk: flutter - path_provider: ^2.0.2 - visibility_detector: ^0.2.0 + path_provider: ^2.0.9 + visibility_detector: ^0.2.2 collection: ^1.15.0 dev_dependencies: diff --git a/pubspec.lock b/pubspec.lock index 05d2839a1..89edea80e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -104,7 +104,7 @@ packages: name: flutter_widget_from_html_core url: "https://pub.dartlang.org" source: hosted - version: "0.8.4" + version: "0.8.5+1" fwfh_text_style: dependency: transitive description: @@ -147,6 +147,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" meta: dependency: "direct main" description: @@ -167,7 +174,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.9" path_provider_android: dependency: transitive description: @@ -284,7 +291,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.4.8" typed_data: dependency: transitive description: @@ -312,7 +319,7 @@ packages: name: wakelock url: "https://pub.dartlang.org" source: hosted - version: "0.5.6" + version: "0.6.1+2" wakelock_macos: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f5bdc07c8..6a43d2e65 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: better_player description: Advanced video player based on video_player and Chewie. It's solves many typical use cases and it's easy to run. -version: 0.0.81 +version: 0.0.82 # Disabled because of warning from analyzer # authors: # - Jakub Homlala @@ -15,13 +15,13 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.4 - wakelock: ^0.5.6 + wakelock: ^0.6.1+2 meta: ^1.7.0 - flutter_widget_from_html_core: ^0.8.4 + flutter_widget_from_html_core: ^0.8.5+1 visibility_detector: ^0.2.2 - path_provider: ^2.0.8 + path_provider: ^2.0.9 collection: ^1.15.0 - xml: ^5.3.1 + xml: ^5.3.0 dev_dependencies: lint: ^1.8.1 diff --git a/test/mock_method_channel.dart b/test/mock_method_channel.dart index c8b01865c..2e256ab59 100644 --- a/test/mock_method_channel.dart +++ b/test/mock_method_channel.dart @@ -42,6 +42,7 @@ class MockMethodChannel { const StandardMethodCodec() .encodeSuccessEnvelope(_getInitResult()), (ByteData? data) {}); + return null; }); eventsChannels.add(eventChannel);