Skip to content

Commit

Permalink
Merge pull request #738 from splendo/bugfix/media-fixes
Browse files Browse the repository at this point in the history
Media iOS Improvements
  • Loading branch information
Daeda88 authored Nov 6, 2023
2 parents b402826 + 1deee40 commit f662c47
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 45 deletions.
14 changes: 7 additions & 7 deletions example/ios/Demo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,14 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSCalendarsUsageDescription</key>
<key>NSAppleMusicUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSCalendarsUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSCameraUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSContactsUsageDescription</key>
Expand All @@ -57,8 +55,10 @@
<string>Demonstrate usage of Kaluga library</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>NSAppleMusicUsageDescription</key>
<string>Demonstrate usage of Kaluga library</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Demo/Media/MediaViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class MediaViewController: UIViewController {

private lazy var viewModel = MediaViewModel(
mediaSurfaceProvider: mediaSurfaceProvider,
builder: DefaultMediaManager.Builder(),
builder: DefaultMediaManager.Builder(settings: DefaultMediaManager.Settings(playInBackground: true, playAfterDeviceUnavailable: true)),
alertPresenterBuilder: AlertPresenter.Builder(viewController: self),
navigator: navigator
)
Expand Down Expand Up @@ -178,7 +178,7 @@ extension MediaViewController: MPMediaPickerControllerDelegate {
if !mediaItemCollection.items.isEmpty {
let item = mediaItemCollection.items[0]
if let url = item.value(forProperty: MPMediaItemPropertyAssetURL) as? NSURL {
viewModel.didSelectFileAt(source: MediaSource.URL(url: url as URL))
viewModel.didSelectFileAt(source: MediaSource.URL(url: url as URL, options: [MediaSource.URLOptionPreferPreciseDurationAndTiming(isPreferred: true)]))
} else {
viewModel.didSelectFileAt(source: nil)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@ package com.splendo.kaluga.example.shared.viewmodel.datetime
import com.splendo.kaluga.alerts.Alert
import com.splendo.kaluga.alerts.AlertPresenter
import com.splendo.kaluga.alerts.buildActionSheet
import com.splendo.kaluga.alerts.buildAlert
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel
import com.splendo.kaluga.base.text.DateFormatStyle
import com.splendo.kaluga.base.text.KalugaDateFormatter
import com.splendo.kaluga.base.utils.DefaultKalugaDate
import com.splendo.kaluga.base.utils.KalugaDate
import com.splendo.kaluga.base.utils.KalugaTimeZone
import com.splendo.kaluga.base.utils.TimeZoneNameStyle
import com.splendo.kaluga.base.utils.utc
import com.splendo.kaluga.datetime.timer.RecurringTimer
import com.splendo.kaluga.datetime.timer.Timer
import com.splendo.kaluga.datetime.timer.elapsed
Expand Down Expand Up @@ -83,7 +80,7 @@ class TimerViewModel(private val alertPresenterBuilder: AlertPresenter.Builder)
emit(Unit)
delay(100)
}
}
},
) { format, _ ->
format.format(DefaultKalugaDate.now())
}.toInitializedObservable("", coroutineScope)
Expand Down
20 changes: 20 additions & 0 deletions media/api/androidLib/media.api
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public abstract class com/splendo/kaluga/media/BaseMediaManager : com/splendo/ka
protected abstract fun handleCreatePlayableMedia (Lcom/splendo/kaluga/media/MediaSource;)Lcom/splendo/kaluga/media/PlayableMedia;
protected fun handleError (Lcom/splendo/kaluga/media/PlaybackError;)V
protected fun handlePrepared (Lcom/splendo/kaluga/media/PlayableMedia;)V
protected fun handleRateChanged (F)V
protected abstract fun handleReset ()V
protected fun handleSeekCompleted (Z)V
public final fun reset ()V
Expand Down Expand Up @@ -120,6 +121,17 @@ public final class com/splendo/kaluga/media/MediaManager$Event$DidPrepare : com/
public fun toString ()Ljava/lang/String;
}

public final class com/splendo/kaluga/media/MediaManager$Event$RateDidChange : com/splendo/kaluga/media/MediaManager$Event {
public fun <init> (F)V
public final fun component1 ()F
public final fun copy (F)Lcom/splendo/kaluga/media/MediaManager$Event$RateDidChange;
public static synthetic fun copy$default (Lcom/splendo/kaluga/media/MediaManager$Event$RateDidChange;FILjava/lang/Object;)Lcom/splendo/kaluga/media/MediaManager$Event$RateDidChange;
public fun equals (Ljava/lang/Object;)Z
public final fun getNewRate ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/splendo/kaluga/media/MediaPlayer : com/splendo/kaluga/media/MediaSurfaceController, com/splendo/kaluga/media/VolumeController {
public abstract fun awaitCompletion (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun close ()V
Expand Down Expand Up @@ -337,6 +349,10 @@ public final class com/splendo/kaluga/media/PlaybackError$TimedOut : com/splendo
public static final field INSTANCE Lcom/splendo/kaluga/media/PlaybackError$TimedOut;
}

public final class com/splendo/kaluga/media/PlaybackError$Uninitalized : com/splendo/kaluga/media/PlaybackError {
public static final field INSTANCE Lcom/splendo/kaluga/media/PlaybackError$Uninitalized;
}

public final class com/splendo/kaluga/media/PlaybackError$Unknown : com/splendo/kaluga/media/PlaybackError {
public static final field INSTANCE Lcom/splendo/kaluga/media/PlaybackError$Unknown;
}
Expand Down Expand Up @@ -437,9 +453,12 @@ public final class com/splendo/kaluga/media/PlaybackState$LoopMode$NotLooping :

public abstract interface class com/splendo/kaluga/media/PlaybackState$Paused : com/splendo/kaluga/media/PlaybackState$Started {
public abstract fun getPlay ()Lkotlin/jvm/functions/Function1;
public abstract fun playWithUpdatedPlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
public abstract fun updatePlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
}

public final class com/splendo/kaluga/media/PlaybackState$Paused$DefaultImpls {
public static fun playWithUpdatedPlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$Paused;Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
public static fun remain (Lcom/splendo/kaluga/media/PlaybackState$Paused;)Lkotlin/jvm/functions/Function1;
}

Expand All @@ -461,6 +480,7 @@ public final class com/splendo/kaluga/media/PlaybackState$PlaybackParameters {
public abstract interface class com/splendo/kaluga/media/PlaybackState$Playing : com/splendo/kaluga/media/PlaybackState$PlayingOrCompleted, com/splendo/kaluga/media/PlaybackState$Started {
public abstract fun getCompletedLoop ()Lkotlin/jvm/functions/Function1;
public abstract fun getPause ()Lkotlin/jvm/functions/Function1;
public abstract fun updatePlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
}

public final class com/splendo/kaluga/media/PlaybackState$Playing$DefaultImpls {
Expand Down
20 changes: 20 additions & 0 deletions media/api/jvm/media.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public abstract class com/splendo/kaluga/media/BaseMediaManager : com/splendo/ka
protected abstract fun handleCreatePlayableMedia (Lcom/splendo/kaluga/media/MediaSource;)Lcom/splendo/kaluga/media/PlayableMedia;
protected fun handleError (Lcom/splendo/kaluga/media/PlaybackError;)V
protected fun handlePrepared (Lcom/splendo/kaluga/media/PlayableMedia;)V
protected fun handleRateChanged (F)V
protected abstract fun handleReset ()V
protected fun handleSeekCompleted (Z)V
public final fun reset ()V
Expand Down Expand Up @@ -111,6 +112,17 @@ public final class com/splendo/kaluga/media/MediaManager$Event$DidPrepare : com/
public fun toString ()Ljava/lang/String;
}

public final class com/splendo/kaluga/media/MediaManager$Event$RateDidChange : com/splendo/kaluga/media/MediaManager$Event {
public fun <init> (F)V
public final fun component1 ()F
public final fun copy (F)Lcom/splendo/kaluga/media/MediaManager$Event$RateDidChange;
public static synthetic fun copy$default (Lcom/splendo/kaluga/media/MediaManager$Event$RateDidChange;FILjava/lang/Object;)Lcom/splendo/kaluga/media/MediaManager$Event$RateDidChange;
public fun equals (Ljava/lang/Object;)Z
public final fun getNewRate ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/splendo/kaluga/media/MediaPlayer : com/splendo/kaluga/media/MediaSurfaceController, com/splendo/kaluga/media/VolumeController {
public abstract fun awaitCompletion (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun close ()V
Expand Down Expand Up @@ -281,6 +293,10 @@ public final class com/splendo/kaluga/media/PlaybackError$TimedOut : com/splendo
public static final field INSTANCE Lcom/splendo/kaluga/media/PlaybackError$TimedOut;
}

public final class com/splendo/kaluga/media/PlaybackError$Uninitalized : com/splendo/kaluga/media/PlaybackError {
public static final field INSTANCE Lcom/splendo/kaluga/media/PlaybackError$Uninitalized;
}

public final class com/splendo/kaluga/media/PlaybackError$Unknown : com/splendo/kaluga/media/PlaybackError {
public static final field INSTANCE Lcom/splendo/kaluga/media/PlaybackError$Unknown;
}
Expand Down Expand Up @@ -381,9 +397,12 @@ public final class com/splendo/kaluga/media/PlaybackState$LoopMode$NotLooping :

public abstract interface class com/splendo/kaluga/media/PlaybackState$Paused : com/splendo/kaluga/media/PlaybackState$Started {
public abstract fun getPlay ()Lkotlin/jvm/functions/Function1;
public abstract fun playWithUpdatedPlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
public abstract fun updatePlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
}

public final class com/splendo/kaluga/media/PlaybackState$Paused$DefaultImpls {
public static fun playWithUpdatedPlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$Paused;Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
public static fun remain (Lcom/splendo/kaluga/media/PlaybackState$Paused;)Lkotlin/jvm/functions/Function1;
}

Expand All @@ -405,6 +424,7 @@ public final class com/splendo/kaluga/media/PlaybackState$PlaybackParameters {
public abstract interface class com/splendo/kaluga/media/PlaybackState$Playing : com/splendo/kaluga/media/PlaybackState$PlayingOrCompleted, com/splendo/kaluga/media/PlaybackState$Started {
public abstract fun getCompletedLoop ()Lkotlin/jvm/functions/Function1;
public abstract fun getPause ()Lkotlin/jvm/functions/Function1;
public abstract fun updatePlaybackParameters (Lcom/splendo/kaluga/media/PlaybackState$PlaybackParameters;)Lkotlin/jvm/functions/Function1;
}

public final class com/splendo/kaluga/media/PlaybackState$Playing$DefaultImpls {
Expand Down
13 changes: 12 additions & 1 deletion media/src/commonMain/kotlin/MediaManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
Expand Down Expand Up @@ -81,6 +82,12 @@ interface MediaManager : VolumeController, MediaSurfaceController {
*/
data class DidFailWithError(val error: PlaybackError) : Event()

/**
* An [Event] indicating the rate was changed
* @property newRate the new rate at which playback occurs
*/
data class RateDidChange(val newRate: Float) : Event()

/**
* An [Event] indicating playback completed
*/
Expand Down Expand Up @@ -153,7 +160,7 @@ interface MediaManager : VolumeController, MediaSurfaceController {
*/
abstract class BaseMediaManager(private val mediaSurfaceProvider: MediaSurfaceProvider?, coroutineContext: CoroutineContext) :
MediaManager,
CoroutineScope by CoroutineScope(coroutineContext + CoroutineName("MediaManager")) {
CoroutineScope by CoroutineScope(coroutineContext + Job(coroutineContext.job) + CoroutineName("MediaManager")) {

/**
* Builder for creating a [BaseMediaManager]
Expand Down Expand Up @@ -207,6 +214,10 @@ abstract class BaseMediaManager(private val mediaSurfaceProvider: MediaSurfacePr
_events.trySend(MediaManager.Event.DidComplete)
}

protected open fun handleRateChanged(newRate: Float) {
_events.trySend(MediaManager.Event.RateDidChange(newRate))
}

final override suspend fun seekTo(duration: Duration): Boolean {
val result = CompletableDeferred<Boolean>()
return seekMutex.withLock {
Expand Down
5 changes: 4 additions & 1 deletion media/src/commonMain/kotlin/MediaPlayer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.splendo.kaluga.media.MediaPlayer.Controls.Stop
import com.splendo.kaluga.media.MediaPlayer.Controls.Unpause
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
Expand All @@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.isActive
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.js.JsName
Expand Down Expand Up @@ -265,7 +267,7 @@ interface MediaPlayer : VolumeController, MediaSurfaceController {
class DefaultMediaPlayer(
createPlaybackStateRepo: (CoroutineContext) -> BasePlaybackStateRepo,
coroutineContext: CoroutineContext,
) : MediaPlayer, CoroutineScope by CoroutineScope(coroutineContext + CoroutineName("MediaPlayer")) {
) : MediaPlayer, CoroutineScope by CoroutineScope(coroutineContext + Job(coroutineContext.job) + CoroutineName("MediaPlayer")) {

/**
* Constructor that provides a [BaseMediaManager] to manage media playback
Expand Down Expand Up @@ -466,6 +468,7 @@ class DefaultMediaPlayer(
is PlaybackState.Completed -> emit(Unit)
is PlaybackState.Closed -> throw PlaybackError.PlaybackHasEnded
is PlaybackState.Error -> throw state.error
is PlaybackState.Uninitialized -> throw PlaybackError.Uninitalized
is PlaybackState.Active -> {} // Wait until completed
}
}.first()
Expand Down
18 changes: 18 additions & 0 deletions media/src/commonMain/kotlin/PlaybackState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ sealed class PlaybackError : Exception() {
*/
object PlaybackHasEnded : PlaybackError()

/**
* A [PlaybackError] that indicates that playback has not been initialized.
*/
object Uninitalized : PlaybackError()

/**
* An unknown [PlaybackError].
*/
Expand Down Expand Up @@ -234,6 +239,8 @@ sealed interface PlaybackState : KalugaState {
* Transitions into a [PlayingOrCompleted] state
*/
val completedLoop: suspend () -> PlayingOrCompleted

override fun updatePlaybackParameters(new: PlaybackParameters): suspend () -> Playing
}

/**
Expand All @@ -245,6 +252,17 @@ sealed interface PlaybackState : KalugaState {
* Transitions back into a [Playing] state
*/
val play: suspend () -> Playing

/**
* Will transition into a [Playing] state with new [PlaybackParameters]
* @param new the [PlaybackParameters] to update to
* @return a method for transitioning into a new [Playing] state
*/
fun playWithUpdatedPlaybackParameters(new: PlaybackParameters) = suspend {
updatePlaybackParameters(new)().play()
}

override fun updatePlaybackParameters(new: PlaybackParameters): suspend () -> Paused
}

/**
Expand Down
16 changes: 16 additions & 0 deletions media/src/commonMain/kotlin/PlaybackStateRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ open class PlaybackStateRepo(
when (event) {
is MediaManager.Event.DidPrepare -> takeAndChangeState(PlaybackState.Initialized::class) { it.prepared(event.playableMedia) }
is MediaManager.Event.DidFailWithError -> takeAndChangeState(PlaybackState.Active::class) { it.failWithError(event.error) }
is MediaManager.Event.RateDidChange -> updateToRate(event.newRate)
is MediaManager.Event.DidComplete -> takeAndChangeState(PlaybackState.Playing::class) { it.completedLoop }
is MediaManager.Event.DidEnd -> takeAndChangeState(PlaybackState.Active::class) { it.end }
}
Expand All @@ -75,4 +76,19 @@ open class PlaybackStateRepo(
mediaManager.close()
}
}

private suspend fun updateToRate(newRate: Float) = takeAndChangeState(PlaybackState.Started::class) { state ->
when (state) {
is PlaybackState.Playing -> when (newRate) {
0.0f -> state.pause
state.playbackParameters.rate -> state.remain()
else -> state.updatePlaybackParameters(state.playbackParameters.copy(rate = newRate))
}
is PlaybackState.Paused -> when (newRate) {
0.0f -> state.remain()
state.playbackParameters.rate -> state.play
else -> state.playWithUpdatedPlaybackParameters(state.playbackParameters.copy(rate = newRate))
}
}
}
}
Loading

0 comments on commit f662c47

Please sign in to comment.