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

Feature/daterange cue support #227

Merged
merged 26 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b1327d7
Add hlsDateRange config setting
wvanhaevre Nov 10, 2023
fd446c4
Add DateRangeCue as TextTrackCue extension
wvanhaevre Nov 10, 2023
e3701af
Add textTrackType daterange
wvanhaevre Nov 10, 2023
0c83fcf
Process customAttributes for dateRange cues
wvanhaevre Nov 10, 2023
69d6335
Make metadata tracks hidden by default
wvanhaevre Nov 10, 2023
cf9d914
Process bridged hlsDateRange feature config for iOS
wvanhaevre Nov 10, 2023
5ddb03f
Bridge customAttributes for DateRangeCues on iOS
wvanhaevre Nov 10, 2023
1df1258
Set hlsDateRange config
wvanhaevre Nov 10, 2023
bce9c78
Fix const keyword
tvanlaerhoven Nov 14, 2023
e18e77c
Pass hlsDateRange to Android player config
tvanlaerhoven Nov 14, 2023
fd8d8f1
Add extra data range cue properties
tvanlaerhoven Nov 14, 2023
6dbc1c5
Serialize date range cue on Android
tvanlaerhoven Nov 14, 2023
337b638
Set metadata tracks to hidden mode by default on Android
tvanlaerhoven Nov 14, 2023
7516b39
Add remaining date range cue properties
tvanlaerhoven Nov 14, 2023
fb28d71
Add daterange example source
tvanlaerhoven Nov 14, 2023
74fd456
Fix duration & plannedDuration in msec
tvanlaerhoven Nov 14, 2023
fe7cedc
Fix duration & plannedDuration in msec on Android
tvanlaerhoven Nov 14, 2023
e074d06
Fix documentation
tvanlaerhoven Nov 14, 2023
6ec8545
Add eventstream text track type
tvanlaerhoven Nov 14, 2023
dee5290
Fix attributes class
tvanlaerhoven Nov 14, 2023
0db8b56
Add isDateRangeCue convenience method
tvanlaerhoven Nov 14, 2023
d88d9e4
Normalize cue
tvanlaerhoven Nov 14, 2023
4fb025e
Encode possible NaN or Infinity values
tvanlaerhoven Nov 14, 2023
e191f5e
Add additional dateRangeCue properties for iOS
wvanhaevre Nov 14, 2023
886e222
Provide DateRangeCue duration in msec
wvanhaevre Nov 17, 2023
9407574
Calculate start- and endTime for DateRangeCues based on currentTime a…
wvanhaevre Nov 20, 2023
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
4 changes: 4 additions & 0 deletions android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ private const val PROP_PRELOAD = "preload"
private const val PROP_UI_ENABLED = "uiEnabled"
private const val PROP_CAST_STRATEGY = "strategy"
private const val PROP_RETRY_CONFIG = "retryConfiguration"
private const val PROP_HLS_DATE_RANGE = "hlsDateRange"
private const val PROP_RETRY_MAX_RETRIES = "maxRetries"
private const val PROP_RETRY_MIN_BACKOFF = "minimumBackoff"
private const val PROP_RETRY_MAX_BACKOFF = "maximumBackoff"
Expand All @@ -44,6 +45,9 @@ class PlayerConfigAdapter(private val configProps: ReadableMap?) {
if (hasKey(PROP_RETRY_CONFIG)) {
networkConfiguration(networkConfig())
}
if (hasKey(PROP_HLS_DATE_RANGE)) {
hlsDateRange(getBoolean(PROP_HLS_DATE_RANGE))
}
pipConfiguration(PipConfiguration.Builder().build())
}
}.build()
Expand Down
5 changes: 5 additions & 0 deletions android/src/main/java/com/theoplayer/PlayerEventEmitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.theoplayer.android.api.player.track.mediatrack.quality.AudioQuality
import com.theoplayer.android.api.player.track.mediatrack.quality.Quality
import com.theoplayer.android.api.player.track.mediatrack.quality.VideoQuality
import com.theoplayer.android.api.player.track.texttrack.TextTrack
import com.theoplayer.android.api.player.track.texttrack.TextTrackKind
import com.theoplayer.android.api.player.track.texttrack.TextTrackMode
import com.theoplayer.cast.CastEventAdapter
import com.theoplayer.presentation.PresentationModeChangeContext
Expand Down Expand Up @@ -474,6 +475,10 @@ class PlayerEventEmitter internal constructor(
}

private fun onTextTrackAdd(event: AddTrackEvent) {
// By default, set metadata tracks to mode 'hidden', until we add an API to set the mode.
if (event.track.kind == TextTrackKind.METADATA.type) {
event.track.mode = TextTrackMode.HIDDEN
}
dispatchTextTrackEvent(TrackEventType.ADD_TRACK, event.track)
}

Expand Down
49 changes: 47 additions & 2 deletions android/src/main/java/com/theoplayer/track/TrackListAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.theoplayer.android.api.player.track.mediatrack.quality.Quality
import com.theoplayer.android.api.player.track.mediatrack.quality.AudioQuality
import com.theoplayer.android.api.player.track.mediatrack.quality.VideoQuality
import com.theoplayer.android.api.player.track.mediatrack.MediaTrackList
import com.theoplayer.android.api.player.track.texttrack.cue.DateRangeCue
import com.theoplayer.util.TypeUtils

private const val PROP_ID = "id"
Expand All @@ -35,6 +36,16 @@ private const val PROP_STARTTIME = "startTime"
private const val PROP_ENDTIME = "endTime"
private const val PROP_CUES = "cues"
private const val PROP_CUE_CONTENT = "content"
private const val PROP_ATTRIBUTE_CLASS = "class"
private const val PROP_STARTDATE = "startDate"
private const val PROP_ENDDATE = "endDate"
private const val PROP_DURATION = "duration"
private const val PROP_PLANNED_DURATION = "plannedDuration"
private const val PROP_END_ON_NEXT = "endOnNext"
private const val PROP_SCTE35CMD = "scte35Cmd"
private const val PROP_SCTE35OUT = "scte35Out"
private const val PROP_SCTE35IN = "scte35In"
private const val PROP_CUSTOM_ATTRIBUTES = "customAttributes"

object TrackListAdapter {

Expand Down Expand Up @@ -85,6 +96,38 @@ object TrackListAdapter {
content.optString("content") ?: content.optString("contentString")
)
}

if (cue is DateRangeCue) {
cue.attributeClass?.run {
cuePayload.putString(PROP_ATTRIBUTE_CLASS, this)
}
cuePayload.putDouble(PROP_STARTDATE, cue.startDate.time.toDouble())
cue.endDate?.run {
cuePayload.putDouble(PROP_ENDDATE, this.time.toDouble())
}
cue.duration?.run {
cuePayload.putDouble(PROP_DURATION, TypeUtils.encodeInfNan(1e3 * this))
}
cue.plannedDuration?.run {
cuePayload.putDouble(PROP_PLANNED_DURATION, TypeUtils.encodeInfNan(1e3 * this))
}
cuePayload.putBoolean(PROP_END_ON_NEXT, cue.isEndOnNext)
cue.customAttributes?.asMap()?.run {
val attributes = Arguments.createMap()
forEach { (key, value) ->
when (value) {
is String -> attributes.putString(key, value)
is Boolean -> attributes.putBoolean(key, value)
is Int -> attributes.putInt(key, value)
is Double -> attributes.putDouble(key, value)
// TODO: support array & sub-objects
}
}
cuePayload.putMap(PROP_CUSTOM_ATTRIBUTES, attributes)
}
// TODO: Add SCTE marker properties
}

return cuePayload
}

Expand Down Expand Up @@ -128,7 +171,8 @@ object TrackListAdapter {
qualityList?.forEach { quality ->
qualities.pushMap(fromAudioQuality(quality))
}
} catch (ignore: NullPointerException) {}
} catch (ignore: NullPointerException) {
}
audioTrackPayload.putArray(PROP_QUALITIES, qualities)
val activeQuality = audioTrack.activeQuality
if (activeQuality != null) {
Expand Down Expand Up @@ -178,7 +222,8 @@ object TrackListAdapter {
qualities.pushMap(fromVideoQuality(quality as VideoQuality))
}
}
} catch (ignore: java.lang.NullPointerException) {}
} catch (ignore: java.lang.NullPointerException) {
}
videoTrackPayload.putArray(PROP_QUALITIES, qualities)
val activeQuality = videoTrack.activeQuality
if (activeQuality != null) {
Expand Down
1 change: 1 addition & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const playerConfig: PlayerConfiguration = {
// Without a license, only demo sources hosted on '*.theoplayer.com' domains can be played.
license: undefined,
chromeless: true,
hlsDateRange: true,
libraryLocation: 'theoplayer',
cast: {
chromecast: {
Expand Down
20 changes: 19 additions & 1 deletion example/src/custom/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,24 @@
}
}
},
{
"name": "HLS - VOD - Clear - DateRange Metadata",
"os": [
"ios",
"web",
"android"
],
"source": {
"sources": {
"src": "https://cdn.theoplayer.com/video/star_wars_episode_vii-the_force_awakens_official_comic-con_2015_reel_(2015)/index-daterange.m3u8",
"type": "application/x-mpegurl"
},
"metadata": {
"title": "Star Wars Episode VII - The Force Awakens Official Comic-Con 2015 Reel",
"displayIconUri": "https://theoplayer-cdn.s3.eu-west-1.amazonaws.com/react-native-theoplayer/temp/THEOPlayer-200x200.png"
}
}
},
{
"name": "HLS - LIVE - Clear - TOS",
"os": [
Expand Down Expand Up @@ -472,4 +490,4 @@
}
}
}
]
]
38 changes: 21 additions & 17 deletions ios/THEOplayerRCTTextTrackEventHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ class THEOplayerRCTTextTrackEventHandler {
}

// ADD_TRACK
self.addTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.ADD_TRACK) { [weak self] event in
guard let welf = self else { return }
self.addTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.ADD_TRACK) { [weak self, weak player] event in
guard let welf = self, let wplayer = player else { return }
if let forwardedTextTrackListEvent = welf.onNativeTextTrackListEvent,
let textTrack = event.track as? TextTrack {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ADD_TRACK event from THEOplayer textTrack list: trackUid = \(textTrack.uid)") }
// trigger tracklist event
forwardedTextTrackListEvent([
"track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack),
"track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: wplayer),
"type" : TrackListEventType.ADD_TRACK.rawValue
])
// start listening for cue events on this track and keep listener for later removal
Expand All @@ -77,14 +77,14 @@ class THEOplayerRCTTextTrackEventHandler {
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] AddTrack listener attached to THEOplayer textTrack list") }

// REMOVE_TRACK
self.removeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.REMOVE_TRACK) { [weak self] event in
guard let welf = self else { return }
self.removeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.REMOVE_TRACK) { [weak self, weak player] event in
guard let welf = self, let wplayer = player else { return }
if let forwardedTextTrackListEvent = welf.onNativeTextTrackListEvent,
let textTrack = event.track as? TextTrack {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received REMOVE_TRACK event from THEOplayer textTrack list: trackUid = \(textTrack.uid)") }
// trigger tracklist event
forwardedTextTrackListEvent([
"track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack),
"track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: wplayer),
"type" : TrackListEventType.REMOVE_TRACK.rawValue
])
// stop listening for cue events on this track
Expand All @@ -109,14 +109,14 @@ class THEOplayerRCTTextTrackEventHandler {
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] RemoveTrack listener attached to THEOplayer textTrack list") }

// CHANGE
self.changeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.CHANGE) { [weak self] event in
guard let welf = self else { return }
self.changeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.CHANGE) { [weak self, weak player] event in
guard let welf = self, let wplayer = player else { return }
if let forwardedTextTrackListEvent = welf.onNativeTextTrackListEvent,
let textTrack = event.track as? TextTrack {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received CHANGE event from THEOplayer textTrack list: trackUid = \(textTrack.uid)") }
// trigger tracklist event
forwardedTextTrackListEvent([
"track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack),
"track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: wplayer),
"type" : TrackListEventType.CHANGE_TRACK.rawValue
])
}
Expand Down Expand Up @@ -176,48 +176,52 @@ class THEOplayerRCTTextTrackEventHandler {
// MARK: - dynamic textTrack Listeners
private func addCueListener(_ event: AddCueEvent) {
if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
let textTrack = event.cue.track {
let textTrack = event.cue.track,
let player = self.player {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ADD_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
forwardedTextTrackEvent([
"trackUid" : textTrack.uid,
"type": TrackCueEventType.ADD_CUE.rawValue,
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
])
}
}

private func removeCueListener(_ event: RemoveCueEvent) {
if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
let textTrack = event.cue.track {
let textTrack = event.cue.track,
let player = self.player {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received REMOVE_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
forwardedTextTrackEvent([
"trackUid" : textTrack.uid,
"type": TrackCueEventType.REMOVE_CUE.rawValue,
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
])
}
}

private func enterCueListener(_ event: EnterCueEvent) {
if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
let textTrack = event.cue.track {
let textTrack = event.cue.track,
let player = self.player {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ENTER_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
forwardedTextTrackEvent([
"trackUid" : textTrack.uid,
"type": TrackCueEventType.ENTER_CUE.rawValue,
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
])
}
}

private func exitCueListener(_ event: ExitCueEvent) {
if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
let textTrack = event.cue.track {
let textTrack = event.cue.track,
let player = self.player {
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received EXIT_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
forwardedTextTrackEvent([
"trackUid" : textTrack.uid,
"type": TrackCueEventType.EXIT_CUE.rawValue,
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
"cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
])
}
}
Expand Down
Loading
Loading