Skip to content

Commit

Permalink
feat: enhance iOS media playback and initialization
Browse files Browse the repository at this point in the history
- Import new dependencies in `MediaPlayerController.kt` for iOS platform interaction.
- Introduce playback management and observation enhancements in MediaPlayerController, including periodic time checks and end-of-time notifications.
- Remove options map related to `cookie` and replace it with `null` in media asset configuration.
- Integrate playback duration calculations into media preparation method.
- Add new function `startObserving` to handle video playback progress and completion actions.
- Add coroutine dispatch context `Dispatchers.IO` in `Platform.ios.kt`.
- Remove cookie parameter and usage from `VideoPlayer` function and related initialization in iOS platform.
- Enhance media player controller initialization by starting video progress observation immediately after media preparation.

Signed-off-by: jnelle <[email protected]>
  • Loading branch information
jnelle committed Jul 13, 2024
1 parent 87a1fd4 commit 9bbbee0
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 5 deletions.
43 changes: 40 additions & 3 deletions composeApp/src/iosMain/kotlin/MediaPlayerController.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
Expand All @@ -13,30 +14,41 @@ import platform.AVFoundation.AVMetadataCommonIdentifierTitle
import platform.AVFoundation.AVMutableMetadataItem
import platform.AVFoundation.AVPlayer
import platform.AVFoundation.AVPlayerItem
import platform.AVFoundation.AVPlayerItemDidPlayToEndTimeNotification
import platform.AVFoundation.AVPlayerTimeControlStatusPlaying
import platform.AVFoundation.AVURLAsset
import platform.AVFoundation.addPeriodicTimeObserverForInterval
import platform.AVFoundation.asset
import platform.AVFoundation.currentItem
import platform.AVFoundation.pause
import platform.AVFoundation.play
import platform.AVFoundation.replaceCurrentItemWithPlayerItem
import platform.AVFoundation.seekToTime
import platform.AVFoundation.timeControlStatus
import platform.AVKit.AVPlayerViewController
import platform.AVKit.setExternalMetadata
import platform.CoreMedia.CMTime
import platform.CoreMedia.CMTimeGetSeconds
import platform.CoreMedia.CMTimeMakeWithSeconds
import platform.Foundation.NSData
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSOperationQueue
import platform.Foundation.NSString
import platform.Foundation.NSURL
import platform.Foundation.dataWithContentsOfURL
import platform.UIKit.UIView
import platform.darwin.Float64
import platform.darwin.NSEC_PER_SEC

@OptIn(ExperimentalForeignApi::class)
class MediaPlayerController(
private val playbackArtworkUrl: String,
private val playbackTitle: String,
private val cookie: Map<Any?, *>?,
) : ViewModel() {
private val avPlayer: AVPlayer = AVPlayer()
private val avPlayerViewController: AVPlayerViewController = AVPlayerViewController()
private lateinit var timeObserver: Any
private var duration: Float64 = 0.0

init {
setUpAudioSession()
Expand All @@ -60,8 +72,7 @@ class MediaPlayerController(
val asset =
AVURLAsset.URLAssetWithURL(
URL = NSURL(string = url),
// https://stackoverflow.com/a/78026972
options = mapOf("AVURLAssetHTTPHeaderFieldsKey" to cookie),
options = null,
)
val playerItem = AVPlayerItem(asset = asset)
viewModelScope.launch(Dispatchers.IO) {
Expand All @@ -71,9 +82,35 @@ class MediaPlayerController(
}
stop()
avPlayer.replaceCurrentItemWithPlayerItem(playerItem)
viewModelScope.launch(Dispatchers.IO) {
duration = CMTimeGetSeconds(avPlayer.currentItem!!.asset.duration())
}
avPlayer.play()
}


@OptIn(ExperimentalForeignApi::class)
fun startObserving(onVideoIsPlayingTask: (progress: Int) -> Unit) {
val interval = CMTimeMakeWithSeconds(30.0, NSEC_PER_SEC.toInt())
timeObserver = avPlayer.addPeriodicTimeObserverForInterval(
interval,
null
) { time: CValue<CMTime> ->
// https://stackoverflow.com/a/55915184/20329236
if (avPlayer.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
val seconds = CMTimeGetSeconds(time)
onVideoIsPlayingTask((seconds / duration * 100).toInt())
}
}

NSNotificationCenter.defaultCenter.addObserverForName(
name = AVPlayerItemDidPlayToEndTimeNotification,
`object` = avPlayer.currentItem,
queue = NSOperationQueue.mainQueue,
usingBlock = {}
)
}

@Suppress("CAST_NEVER_SUCCEEDS")
private fun setupMetadata(): List<AVMutableMetadataItem> {
val title = AVMutableMetadataItem()
Expand Down
5 changes: 3 additions & 2 deletions composeApp/src/iosMain/kotlin/Platform.ios.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.interop.UIKitView
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext

class IOSPlatform : Platform {
Expand All @@ -24,23 +25,23 @@ actual fun getPlatform(): Platform = IOSPlatform()
actual fun VideoPlayer(
modifier: Modifier,
url: String,
cookie: Map<Any?, *>?,
playbackTitle: String,
playbackArtwork: String,
onVideoIsPlayingTask: (progress: Int) -> Unit,
playbackSubtitle: String,
) {
var mediaPlayerController by remember { mutableStateOf<MediaPlayerController?>(null) }
var currentUrl by remember { mutableStateOf(url) }
LaunchedEffect(Unit) {
mediaPlayerController =
withContext(Dispatchers.Main) {
MediaPlayerController(
cookie = cookie,
playbackTitle = playbackTitle,
playbackArtworkUrl = playbackArtwork,
)
}
mediaPlayerController!!.prepare(url)
mediaPlayerController!!.startObserving { onVideoIsPlayingTask(it) }
}

LaunchedEffect(url) {
Expand Down

0 comments on commit 9bbbee0

Please sign in to comment.