Skip to content

Commit

Permalink
feat: added playback
Browse files Browse the repository at this point in the history
  • Loading branch information
francodriansetti committed Mar 14, 2024
1 parent 6c28cf3 commit 6d52d0b
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 86 deletions.
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ android {
viewBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
Expand Down
9 changes: 5 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.android.library") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
// id("com.android.library") version "8.2.2" apply false

id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply true
kotlin("jvm") version "1.9.22"
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply false
kotlin("jvm") version "1.9.0"
// id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false
}

dependencies {
Expand Down
2 changes: 1 addition & 1 deletion playback-sdk-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ android {
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
kotlinCompilerExtensionVersion = "1.5.2"
}

kotlinOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ sealed class SDKError : Throwable() {
object InitializationError : SDKError()
object MissingLicense : SDKError()
object LoadHLSStreamError : SDKError()

object FetchBitmovinLicenseError : SDKError()

}

sealed class PlayBackAPIError : Throwable() {
Expand Down
152 changes: 99 additions & 53 deletions playback-sdk-android/src/main/java/com/streamamg/PlayBackSDKManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,53 @@ import androidx.compose.runtime.Composable
import com.streamamg.api.player.PlayBackAPI
import com.streamamg.api.player.PlayBackAPIService
import com.streamamg.player.ui.PlaybackUIView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import java.net.URL

/**
* Singleton object to manage playback SDK functionalities.
*/
object PlayBackSDKManager {
private var playBackAPI: PlayBackAPI? = null
private var playerInformationAPI: PlayerInformationAPI? = null
private var bitmovinLicense: String? = null
private var amgAPIKey: String? = null

//region Properties

//region Private Properties

private lateinit var playBackAPI: PlayBackAPI
private lateinit var playerInformationAPI: PlayerInformationAPI

private lateinit var amgAPIKey: String
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private lateinit var playBackAPIService: PlayBackAPIService

//endregion

//region Internal Properties

/**
* Base URL for the playback API.
*/
internal var baseURL = "https://api.playback.streamamg.com/v1"
private val coroutineScope = CoroutineScope(Dispatchers.Main)
private var playBackAPIService: PlayBackAPIService? = null
internal lateinit var bitmovinLicense: String
//endregion

//endregion

//region Public methods

//region Initialization

/**
* Initializes the playback SDK.
* @param apiKey The API key for authentication.
* @param baseURL The base URL for the playback API. Default is null.
* @param completion Callback to be invoked upon completion of initialization.
*/
fun initialize(
apiKey: String,
baseURL: String? = null,
completion: (String?, Throwable?) -> Unit
completion: (String?, SDKError?) -> Unit
) {
if (apiKey.isEmpty()) {
completion(null, SDKError.InitializationError)
Expand All @@ -39,94 +66,113 @@ object PlayBackSDKManager {
playBackAPIService = PlayBackAPIService(apiKey)
this.playBackAPI = playBackAPIService

// Fetching Bitmovin license
// Fetching player information
fetchPlayerInfo(completion)
}

private fun fetchPlayerInfo(completion: (String?, Throwable?) -> Unit) {
val playerInformationAPIExist = playerInformationAPI ?: run {
completion(null, SDKError.InitializationError)
return
//endregion

//region Load Player

/**
* Loads the player UI.
* @param entryID The ID of the entry.
* @param authorizationToken The authorization token.
* @param onError Callback for handling errors. Default is null.
* @return Composable function to render the player UI.
*/
@Composable
fun loadPlayer(
entryID: String,
authorizationToken: String,
onError: ((PlayBackAPIError) -> Unit)?
): @Composable () -> Unit {
val playbackUIView = PlaybackUIView(entryID, authorizationToken, onError)

return {
playbackUIView.Render()
}
}

//endregion

//endregion

//region Internal methods

//region Player Information

/**
* Fetches player information including Bitmovin license.
* @param completion Callback to be invoked upon completion of fetching player information.
*/
private fun fetchPlayerInfo(completion: (String?, SDKError?) -> Unit) {
coroutineScope.launch {
try {
val playerInfo = playerInformationAPIExist.getPlayerInformation().first()
val playerInfo = playerInformationAPI.getPlayerInformation().firstOrNull()

// Check if playerInfo is null before accessing its properties
if (playerInfo?.player?.bitmovin?.license.isNullOrEmpty()) {
completion(null, SDKError.MissingLicense)
return@launch
}

// Extract the Bitmovin license
val bitmovinLicense = playerInfo?.player?.bitmovin?.license

// Set the received Bitmovin license
this@PlayBackSDKManager.bitmovinLicense = bitmovinLicense
this@PlayBackSDKManager.bitmovinLicense = bitmovinLicense ?: run {
completion(null, SDKError.MissingLicense)
return@launch
}

// Log success message
Log.d(
"PlayBackSDKManager",
"Bitmovin license fetched successfully: $bitmovinLicense"
)

// Call the completion handler with success
completion(bitmovinLicense, null)
} catch (e: Throwable) {
Log.e("PlayBackSDKManager", "Error fetching Bitmovin license: $e")
completion(null, e)
completion(null, SDKError.FetchBitmovinLicenseError)
}
}
}

//endregion

//region HLS Stream

/**
* Loads the HLS stream.
* @param entryId The ID of the entry.
* @param authorizationToken The authorization token.
* @param completion Callback to be invoked upon completion of loading the HLS stream.
*/
fun loadHLSStream(
entryId: String,
authorizationToken: String?,
completion: (URL?, SDKError?) -> Unit
) {
val playBackAPIExist = playBackAPI ?: run {
completion(null, SDKError.InitializationError)
return
}
GlobalScope.launch(Dispatchers.IO) {
coroutineScope.launch(Dispatchers.IO) {
try {
val videoDetails =
playBackAPIExist.getVideoDetails(entryId, authorizationToken).first()

// Log received video details
Log.d("PlayBackSDKManager", "Received video details: $videoDetails")
playBackAPI.getVideoDetails(entryId, authorizationToken).firstOrNull()

// Extract the HLS stream URL from video details
val hlsURLString = videoDetails.media?.hls
val hlsURLString = videoDetails?.media?.hls
val hlsURL = hlsURLString?.let { URL(it) }

if (hlsURL != null) {
// Call the completion handler with the HLS stream URL
completion(hlsURL, null)
} else {
completion(null, SDKError.LoadHLSStreamError)
}
} catch (e: Throwable) {
Log.e("PlayBackSDKManager", "Error loading HLS stream: $e")
completion(null, SDKError.InitializationError) //TODO: add correct error
completion(null, SDKError.LoadHLSStreamError)
}
}
}

//endregion

@Composable
fun loadPlayer(
entryID: String,
authorizationToken: String,
onError: ((PlayBackAPIError) -> Unit)?
): @Composable () -> Unit {
//endregion

val playbackUIView = PlaybackUIView(entryID, authorizationToken, onError)

// Return a Composable function that renders the playback UI
return {
playbackUIView.Render()
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ internal class PlayBackAPIService(private val apiKey: String) : PlayBackAPI {
emit(responseModel)
} else {
// TODO: handle error
// val errorResponse = connection.errorStream?.let {
// Json.decodeFromString<ErrorResponse>(it.reader().readText())
// }
// val errorMessage = errorResponse?.message ?: "Unknown authentication error"
// throw PlayBackAPIError.apiError(connection.responseCode, errorMessage)
val errorResponse = connection.errorStream?.let {
Json.decodeFromString<ErrorResponse>(it.reader().readText())
}
val errorMessage = errorResponse?.message ?: "Unknown authentication error"
throw PlayBackAPIError.apiError(connection.responseCode, errorMessage)
}
} catch (e: IOException) {
throw PlayBackAPIError.NetworkError(e)
Expand Down
Loading

0 comments on commit 6d52d0b

Please sign in to comment.