Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

- Added support on iOS to push the initial state of the player from the iOS bridge to the React native adapter.
- Added functionality to sync the player state from the native, mobile players to the react native adapter.

### Fixed

Expand Down
37 changes: 24 additions & 13 deletions android/src/main/java/com/theoplayer/PlayerEventEmitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import kotlin.math.floor
private val TAG = PlayerEventEmitter::class.java.name

private const val EVENT_PLAYER_READY = "onNativePlayerReady"
private const val EVENT_PLAYER_STATE_SYNC = "onNativePlayerStateSync"
private const val EVENT_SOURCECHANGE = "onNativeSourceChange"
private const val EVENT_LOADSTART = "onNativeLoadStart"
private const val EVENT_LOADEDMETADATA = "onNativeLoadedMetadata"
Expand Down Expand Up @@ -98,6 +99,7 @@ class PlayerEventEmitter internal constructor(
@Retention(AnnotationRetention.SOURCE)
@StringDef(
EVENT_PLAYER_READY,
EVENT_PLAYER_STATE_SYNC,
EVENT_SOURCECHANGE,
EVENT_LOADSTART,
EVENT_LOADEDMETADATA,
Expand Down Expand Up @@ -135,6 +137,7 @@ class PlayerEventEmitter internal constructor(
companion object {
val Events = arrayOf(
EVENT_PLAYER_READY,
EVENT_PLAYER_STATE_SYNC,
EVENT_SOURCECHANGE,
EVENT_LOADSTART,
EVENT_LOADEDMETADATA,
Expand Down Expand Up @@ -291,13 +294,28 @@ class PlayerEventEmitter internal constructor(

fun preparePlayer(player: Player) {
attachListeners(player)
emitPlayerReady(player)
emitPlayerReady()
}

fun emitPlayerReady(player: Player) {
val payload = Arguments.createMap()
payload.putMap(
EVENT_PROP_STATE,
fun emitPlayerReady() {
// Notify the player is ready
receiveEvent(EVENT_PLAYER_READY, Arguments.createMap().apply {
putMap(EVENT_PROP_STATE, collectPlayerState())
putMap(EVENT_PROP_VERSION, WritableNativeMap().apply {
putString(EVENT_PROP_VERSION, THEOplayerGlobal.getVersion())
putString(EVENT_PROP_SUITE_VERSION, "")
})
})
}

fun emitPlayerStateSync() {
receiveEvent(EVENT_PLAYER_STATE_SYNC, Arguments.createMap().apply {
putMap(EVENT_PROP_STATE, collectPlayerState())
})
}

private fun collectPlayerState(): WritableMap {
return playerView.player?.let { player ->
PayloadBuilder()
.source(player.source)
.currentTime(player.currentTime)
Expand All @@ -315,14 +333,7 @@ class PlayerEventEmitter internal constructor(
.selectedAudioTrack(getSelectedAudioTrack(player))
.selectedVideoTrack(getSelectedVideoTrack(player))
.build()
)
payload.putMap(EVENT_PROP_VERSION, WritableNativeMap().apply {
putString(EVENT_PROP_VERSION, THEOplayerGlobal.getVersion())
putString(EVENT_PROP_SUITE_VERSION, "")
})

// Notify the player is ready
receiveEvent(EVENT_PLAYER_READY, payload)
} ?: Arguments.createMap()
}

private fun emitError(code: String, message: String?) {
Expand Down
1 change: 1 addition & 0 deletions ios/THEOplayerRCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ @interface RCT_EXTERN_MODULE(THEOplayerRCTViewManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(onNativeTHEOliveEvent, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativeTHEOadsEvent, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativeCastEvent, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativePlayerStateSync, RCTDirectEventBlock);

@end

Expand Down
92 changes: 54 additions & 38 deletions ios/THEOplayerRCTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class THEOplayerRCTView: UIView {

// MARK: Events
var onNativePlayerReady: RCTDirectEventBlock?
var onNativePlayerStateSync: RCTDirectEventBlock?

// MARK: Bridged props
private var license: String?
Expand Down Expand Up @@ -210,35 +211,32 @@ public class THEOplayerRCTView: UIView {
}
}

private func initPlayer() -> THEOplayer? {
let config = THEOplayerConfigurationBuilder()
config.pip = self.playerPipConfiguration()
config.hlsDateRange = self.hlsDateRange
config.license = self.license
config.licenseUrl = self.licenseUrl
self.player = THEOplayer(configuration: config.build())

self.initAdsIntegration()
self.initCastIntegration()
self.initTHEOadsIntegration()
self.initTHEOliveIntegration()
self.initMillicastIntegration()
self.initBackgroundAudio()
self.initPip()
return self.player
}

public func notifyNativePlayerReady() {
DispatchQueue.main.async {
let versionString = THEOplayer.version
if let forwardedNativeReady = self.onNativePlayerReady {
var payload: [String: Any] = [:]

// pass initial player state
if let player = self.player {
// collect stored track metadata
let trackInfo = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(
player: player,
metadataTracksInfo: self.mainEventHandler.loadedMetadataAndChapterTracksInfo
)

// build state
payload["state"] = THEOplayerRCTPlayerStateBuilder()
.source(player.source)
.currentTime(player.currentTime)
.currentProgramDateTime(player.currentProgramDateTime)
.paused(player.paused)
.playbackRate(player.playbackRate)
.duration(player.duration)
.volume(player.volume)
.muted(player.muted)
.seekable(player.seekable)
.buffered(player.buffered)
.trackInfo(trackInfo)
.build()
}
payload["state"] = self.collectPlayerStatePayload()

// pass version onfo
payload["version"] = [
Expand All @@ -251,22 +249,28 @@ public class THEOplayerRCTView: UIView {
}
}

private func initPlayer() -> THEOplayer? {
let config = THEOplayerConfigurationBuilder()
config.pip = self.playerPipConfiguration()
config.hlsDateRange = self.hlsDateRange
config.license = self.license
config.licenseUrl = self.licenseUrl
self.player = THEOplayer(configuration: config.build())
private func collectPlayerStatePayload() -> [String:Any] {
guard let player = self.player else { return [:] }

self.initAdsIntegration()
self.initCastIntegration()
self.initTHEOadsIntegration()
self.initTHEOliveIntegration()
self.initMillicastIntegration()
self.initBackgroundAudio()
self.initPip()
return self.player
// collect stored track metadata
let trackInfo = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(
player: player,
metadataTracksInfo: self.mainEventHandler.loadedMetadataAndChapterTracksInfo
)

return THEOplayerRCTPlayerStateBuilder()
.source(player.source)
.currentTime(player.currentTime)
.currentProgramDateTime(player.currentProgramDateTime)
.paused(player.paused)
.playbackRate(player.playbackRate)
.duration(player.duration)
.volume(player.volume)
.muted(player.muted)
.seekable(player.seekable)
.buffered(player.buffered)
.trackInfo(trackInfo)
.build()
}

func processMetadataAndChapterTracks(trackDescriptions: [TextTrackDescription]?) {
Expand All @@ -276,6 +280,12 @@ public class THEOplayerRCTView: UIView {
}
}

public func syncPlayerState() {
if let forwardedPlayerStateSync = self.onNativePlayerStateSync {
forwardedPlayerStateSync(["state": self.collectPlayerStatePayload()])
}
}

// MARK: - Property bridging (config)

@objc(setConfig:)
Expand All @@ -299,14 +309,20 @@ public class THEOplayerRCTView: UIView {
self.notifyNativePlayerReady()
}

// MARK: - VIEW READY event bridging
// MARK: - VIEW event bridging

@objc(setOnNativePlayerReady:)
func setOnNativePlayerReady(nativePlayerReady: @escaping RCTDirectEventBlock) {
self.onNativePlayerReady = nativePlayerReady
if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativePlayerReady prop set.") }
}

@objc(setOnNativePlayerStateSync:)
func setonNativePlayerStateSync(nativePlayerStateSync: @escaping RCTDirectEventBlock) {
self.onNativePlayerStateSync = nativePlayerStateSync
if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativePlayerStateSync prop set.") }
}

// MARK: - Listener based MAIN event bridging

@objc(setOnNativePlay:)
Expand Down
7 changes: 7 additions & 0 deletions src/internal/THEOplayerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface THEOplayerRCTViewProps {
style?: StyleProp<ViewStyle>;
config?: PlayerConfiguration;
onNativePlayerReady: (event: NativeSyntheticEvent<NativePlayerStateEvent>) => void;
onNativePlayerStateSync: (event: NativeSyntheticEvent<NativePlayerStateEvent>) => void;
onNativeSourceChange: () => void;
onNativeLoadStart: () => void;
onNativeLoadedData: () => void;
Expand Down Expand Up @@ -200,6 +201,11 @@ export class THEOplayerView extends PureComponent<React.PropsWithChildren<THEOpl
});
};

private _onNativePlayerStateSync = (event: NativeSyntheticEvent<NativePlayerStateEvent>) => {
const { state } = event.nativeEvent;
this._facade.updateStateFromNativePlayer_(state);
};

private _onSourceChange = () => {
this.reset();
this._facade.dispatchEvent(new BaseEvent(PlayerEventType.SOURCE_CHANGE));
Expand Down Expand Up @@ -439,6 +445,7 @@ export class THEOplayerView extends PureComponent<React.PropsWithChildren<THEOpl
style={StyleSheet.absoluteFill}
config={config || {}}
onNativePlayerReady={this._onNativePlayerReady}
onNativePlayerStateSync={this._onNativePlayerStateSync}
onNativeSourceChange={this._onSourceChange}
onNativeLoadStart={this._onLoadStart}
onNativeLoadedData={this._onLoadedData}
Expand Down
6 changes: 6 additions & 0 deletions src/internal/adapter/THEOplayerAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ export class THEOplayerAdapter extends DefaultEventDispatcher<PlayerEventMap> im
await this._castAdapter.init_();
}

updateStateFromNativePlayer_(state: NativePlayerState | undefined) {
if (state) {
this._state.apply(state);
}
}

get width(): number | undefined {
return this._state.width;
}
Expand Down
Loading