Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Handle Livestream Not Started State #190

Merged
merged 4 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions player/src/main/java/com/tpstream/player/data/Asset.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ data class Asset(
val currentDateTime = Date()

return when {
liveStream?.status == "Not Started" && liveStream.startTime?.after(currentDateTime) == true ->
liveStream?.noticeMessage != null -> liveStream.noticeMessage
liveStream?.isNotStarted == true && liveStream.startTime?.after(currentDateTime) == true ->
"Live stream is scheduled to start at ${liveStream.getFormattedStartTime()}"
liveStream?.status == "Not Started" ->
liveStream?.isNotStarted == true ->
"Live stream will begin soon."
liveStream?.isEnded == true && liveStream.recordingEnabled && !video.isTranscodingCompleted ->
"Live stream has ended. Recording will be available soon."
Expand Down Expand Up @@ -84,13 +85,26 @@ data class LiveStream(
var recordingEnabled: Boolean,
var enabledDRMForRecording: Boolean,
val enabledDRMForLive: Boolean,
val noticeMessage: String?
) {
val isStreaming: Boolean
get() = status == "Streaming"

val isEnded: Boolean
get() = status == "Completed"

val isNotEnded: Boolean
get() = !isEnded

val isDisconnected: Boolean
get() = status == "Disconnected"

val isNotStarted: Boolean
get() = status == "Not Started"

val isRecording: Boolean
get() = status == "Recording"

fun getFormattedStartTime(): String {
val outputFormat = SimpleDateFormat("MMMM d, yyyy, h:mm a", Locale.getDefault())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ internal fun getDomainLiveStream(asset: NetworkAsset): LiveStream? =
startTime = parseDateTime(startTime),
recordingEnabled = recordingEnabled,
enabledDRMForLive = enabledDRMForLive,
enabledDRMForRecording = enabledDRMForRecording
enabledDRMForRecording = enabledDRMForRecording,
noticeMessage = noticeMessage
)
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ internal data class NetworkAsset(
val enabledDRMForLive: Boolean,

@SerializedName("enable_drm_for_recording")
val enabledDRMForRecording: Boolean
val enabledDRMForRecording: Boolean,

@SerializedName("notice_message")
val noticeMessage: String?
)
}
41 changes: 41 additions & 0 deletions player/src/main/java/com/tpstream/player/ui/TPStreamPlayerView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import com.tpstream.player.offline.DownloadTask
import com.tpstream.player.ui.viewmodel.VideoViewModel
import com.tpstream.player.util.ImageSaver
import com.tpstream.player.util.MarkerState
import com.tpstream.player.util.NetworkClient.Companion.makeHeadRequest
import com.tpstream.player.util.getPlayedStatusArray
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -56,6 +58,7 @@ class TPStreamPlayerView @JvmOverloads constructor(
private var tPStreamPlayerViewCallBack: TPStreamPlayerViewCallBack? = null
private var noticeScreenLayout: LinearLayout? = null
private var noticeMessage: TextView? = null
private val coroutineScope = CoroutineScope(Dispatchers.IO)

init {
registerDownloadListener()
Expand Down Expand Up @@ -288,18 +291,56 @@ class TPStreamPlayerView @JvmOverloads constructor(
val durationSeparator: TextView = playerView.findViewById(R.id.exo_separator)
val liveLabel: RelativeLayout = playerView.findViewById(R.id.live_label)

noticeScreenLayout?.visibility = View.GONE
durationView.visibility = View.GONE
durationSeparator.visibility = View.GONE
liveLabel.visibility = View.VISIBLE
}

private fun showNoticeScreen(asset: Asset) {
displayNoticeMessage(asset)
handleNotStartedLiveStream(asset)
}

private fun displayNoticeMessage(asset: Asset) {
asset.getNoticeMessage()?.let {
noticeMessage!!.text = it
noticeScreenLayout!!.visibility = View.VISIBLE
}
}

private fun handleNotStartedLiveStream(asset: Asset) {
if (asset.liveStream?.isNotStarted == true) {
asset.liveStream?.url?.let { url ->
coroutineScope.launch {
requestWithRetry(url, 10000)
}
}
}
}

private suspend fun requestWithRetry(url: String, delay: Long) {
while (coroutineScope.isActive) {
try {
val responseCode = makeHeadRequest(url)
if (responseCode == 200) {
withContext(Dispatchers.Main) {
player.load(player.params)
}
return
}
} catch (e: Exception) {
println("Request failed: ${e.message}")
}
delay(delay)
}
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
coroutineScope.cancel()
}

override fun getViewModelStore(): ViewModelStore {
return viewModelStore
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ class TpStreamPlayerFragment : Fragment(), DownloadCallback.Listener {

override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
if (isDisconnectedLiveStream(error)){
player.load(player.params)
return
}
val errorPlayerId = SentryLogger.generatePlayerIdString()
SentryLogger.logPlaybackException(error, player?.params, errorPlayerId)
if (error.errorCode == PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED) {
Expand Down Expand Up @@ -308,6 +312,13 @@ class TpStreamPlayerFragment : Fragment(), DownloadCallback.Listener {

}

private fun isDisconnectedLiveStream(error: PlaybackException): Boolean {
player.asset?.liveStream?.let {
return it.isNotEnded && error.errorCode == PlaybackException.ERROR_CODE_IO_UNSPECIFIED
}
return false
}

private fun showErrorMessage(message: String) {
viewBinding.errorMessage.visibility = View.VISIBLE
viewBinding.errorMessage.text = message
Expand Down
10 changes: 9 additions & 1 deletion player/src/main/java/com/tpstream/player/util/NetworkClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import java.net.URL
internal class NetworkClient<T : Any>(val klass: Class<T>) {
companion object {
inline operator fun <reified T : Any>invoke() = NetworkClient(T::class.java)

fun makeHeadRequest(url: String): Int {
val request = Request.Builder().head().url(url).build()
OkHttpClient().newCall(request).execute().use { response ->
return response.code
}
}
}

private var client: OkHttpClient = OkHttpClient();
Expand Down Expand Up @@ -74,4 +81,5 @@ internal class NetworkClient<T : Any>(val klass: Class<T>) {
fun onSuccess(result: T)
fun onFailure(exception: TPException)
}
}
}

Loading