From 28a64079dbc4e1cc7202602fcfe9949e79e3813e Mon Sep 17 00:00:00 2001 From: Syed Ibrahim Date: Tue, 3 Jun 2025 15:56:59 +0530 Subject: [PATCH 1/2] feat: expose player methods in ReactNative --- .../com/tpstreams/TPStreamsRNPlayerView.kt | 65 ++++++ .../tpstreams/TPStreamsRNPlayerViewManager.kt | 43 ++++ package.json | 2 +- src/TPStreamsPlayer.tsx | 194 ++++++++++++++++++ src/TPStreamsPlayerViewNativeComponent.ts | 46 ++++- src/index.tsx | 3 + 6 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 src/TPStreamsPlayer.tsx diff --git a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt index abac91a..8cf115c 100644 --- a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt +++ b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt @@ -2,7 +2,11 @@ package com.tpstreams import android.util.Log import android.widget.FrameLayout +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.events.RCTEventEmitter import com.tpstreams.player.TPStreamsPlayer import com.tpstreams.player.TPStreamsPlayerView @@ -10,6 +14,7 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) private val playerView: TPStreamsPlayerView = TPStreamsPlayerView(context) private var player: TPStreamsPlayer? = null + private val reactContext: ReactContext = context private var videoId: String? = null private var accessToken: String? = null @@ -41,6 +46,66 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) } } + fun play() { + player?.play() + } + + fun pause() { + player?.pause() + } + + fun seekTo(positionMs: Long) { + player?.seekTo(positionMs) + } + + fun setPlaybackSpeed(speed: Float) { + player?.setPlaybackSpeed(speed) + } + + fun getCurrentPosition(): Long { + val position = player?.currentPosition ?: 0L + + val event = Arguments.createMap() + event.putDouble("position", position.toDouble()) + reactContext.getJSModule(RCTEventEmitter::class.java) + .receiveEvent(id, "onCurrentPosition", event) + + return position + } + + fun getDuration(): Long { + val duration = player?.duration ?: 0L + + val event = Arguments.createMap() + event.putDouble("duration", duration.toDouble()) + reactContext.getJSModule(RCTEventEmitter::class.java) + .receiveEvent(id, "onDuration", event) + + return duration + } + + fun isPlaying(): Boolean { + val playing = player?.isPlaying ?: false + + val event = Arguments.createMap() + event.putBoolean("isPlaying", playing) + reactContext.getJSModule(RCTEventEmitter::class.java) + .receiveEvent(id, "onIsPlaying", event) + + return playing + } + + fun getPlaybackSpeed(): Float { + val speed = player?.playbackParameters?.speed ?: 1.0f + + val event = Arguments.createMap() + event.putDouble("speed", speed.toDouble()) + reactContext.getJSModule(RCTEventEmitter::class.java) + .receiveEvent(id, "onPlaybackSpeed", event) + + return speed + } + override fun onDetachedFromWindow() { super.onDetachedFromWindow() try { diff --git a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt index 603b4fb..18fe9d1 100644 --- a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt +++ b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt @@ -8,6 +8,7 @@ import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.viewmanagers.TPStreamsRNPlayerViewManagerInterface import com.facebook.react.viewmanagers.TPStreamsRNPlayerViewManagerDelegate +import com.facebook.react.common.MapBuilder @ReactModule(name = TPStreamsRNPlayerViewManager.NAME) class TPStreamsRNPlayerViewManager : SimpleViewManager(), @@ -26,6 +27,15 @@ class TPStreamsRNPlayerViewManager : SimpleViewManager(), return NAME } + override fun getExportedCustomDirectEventTypeConstants(): Map { + return MapBuilder.builder() + .put("onCurrentPosition", MapBuilder.of("registrationName", "onCurrentPosition")) + .put("onDuration", MapBuilder.of("registrationName", "onDuration")) + .put("onIsPlaying", MapBuilder.of("registrationName", "onIsPlaying")) + .put("onPlaybackSpeed", MapBuilder.of("registrationName", "onPlaybackSpeed")) + .build() + } + public override fun createViewInstance(context: ThemedReactContext): TPStreamsRNPlayerView { return TPStreamsRNPlayerView(context) } @@ -40,6 +50,39 @@ class TPStreamsRNPlayerViewManager : SimpleViewManager(), view.setAccessToken(accessToken) } + override fun play(view: TPStreamsRNPlayerView) { + view.play() + } + + override fun pause(view: TPStreamsRNPlayerView) { + view.pause() + } + + override fun seekTo(view: TPStreamsRNPlayerView, positionMs: Double) { + view.seekTo(positionMs.toLong()) + } + + override fun setPlaybackSpeed(view: TPStreamsRNPlayerView, speed: Float) { + view.setPlaybackSpeed(speed) + } + + override fun getCurrentPosition(view: TPStreamsRNPlayerView) { + view.getCurrentPosition() + } + + override fun getDuration(view: TPStreamsRNPlayerView) { + view.getDuration() + } + + override fun isPlaying(view: TPStreamsRNPlayerView) { + view.isPlaying() + } + + override fun getPlaybackSpeed(view: TPStreamsRNPlayerView) { + view.getPlaybackSpeed() + } + + companion object { const val NAME = "TPStreamsRNPlayerView" } diff --git a/package.json b/package.json index 618d4c0..3ce0d56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-tpstreams", - "version": "0.1.0", + "version": "0.2.0", "description": "Video component for TPStreams", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/src/TPStreamsPlayer.tsx b/src/TPStreamsPlayer.tsx new file mode 100644 index 0000000..b141379 --- /dev/null +++ b/src/TPStreamsPlayer.tsx @@ -0,0 +1,194 @@ +import { forwardRef, useImperativeHandle, useRef, useCallback } from 'react'; +import TPStreamsPlayerView, { + Commands, +} from './TPStreamsPlayerViewNativeComponent'; +import type { NativeProps } from './TPStreamsPlayerViewNativeComponent'; + +// Create a unique ID for each instance to track promises +let nextInstanceId = 0; + +// Type for tracking promises waiting to be resolved +type PromiseMap = { + [key: string]: { + resolve: (value: any) => void; + reject: (reason?: any) => void; + }; +}; + +export interface TPStreamsPlayerRef { + play: () => void; + pause: () => void; + seekTo: (positionMs: number) => void; + setPlaybackSpeed: (speed: number) => void; + getCurrentPosition: () => Promise; + getDuration: () => Promise; + isPlaying: () => Promise; + getPlaybackSpeed: () => Promise; +} + +/** + * TPStreamsPlayer - React component wrapper for TPStreamsPlayerView + * Provides a simple imperative API for controlling the player + */ +const TPStreamsPlayer = forwardRef( + (props, ref) => { + const nativeRef = useRef(null); + const instanceId = useRef(nextInstanceId++); + // Keep track of promises that are waiting for native events + const promiseMap = useRef({}); + + // Event handlers that resolve promises + const onCurrentPosition = useCallback((event: any) => { + const key = `position-${instanceId.current}`; + if (promiseMap.current[key]) { + const handler = promiseMap.current[key]; + if (handler) { + handler.resolve(event.nativeEvent.position); + delete promiseMap.current[key]; + } + } + }, []); + + const onDuration = useCallback((event: any) => { + const key = `duration-${instanceId.current}`; + if (promiseMap.current[key]) { + const handler = promiseMap.current[key]; + if (handler) { + handler.resolve(event.nativeEvent.duration); + delete promiseMap.current[key]; + } + } + }, []); + + const onIsPlaying = useCallback((event: any) => { + const key = `isPlaying-${instanceId.current}`; + if (promiseMap.current[key]) { + const handler = promiseMap.current[key]; + if (handler) { + handler.resolve(event.nativeEvent.isPlaying); + delete promiseMap.current[key]; + } + } + }, []); + + const onPlaybackSpeed = useCallback((event: any) => { + const key = `playbackSpeed-${instanceId.current}`; + if (promiseMap.current[key]) { + const handler = promiseMap.current[key]; + if (handler) { + handler.resolve(event.nativeEvent.speed); + delete promiseMap.current[key]; + } + } + }, []); + + useImperativeHandle(ref, () => ({ + play: () => { + if (nativeRef.current) { + Commands.play(nativeRef.current); + } + }, + pause: () => { + if (nativeRef.current) { + Commands.pause(nativeRef.current); + } + }, + seekTo: (positionMs: number) => { + if (nativeRef.current) { + Commands.seekTo(nativeRef.current, positionMs); + } + }, + setPlaybackSpeed: (speed: number) => { + if (nativeRef.current) { + Commands.setPlaybackSpeed(nativeRef.current, speed); + } + }, + getCurrentPosition: () => { + return new Promise((resolve, reject) => { + if (nativeRef.current) { + const key = `position-${instanceId.current}`; + promiseMap.current[key] = { resolve, reject }; + Commands.getCurrentPosition(nativeRef.current); + + // Set a timeout to reject the promise if it's not resolved in time + setTimeout(() => { + if (promiseMap.current[key]) { + reject(new Error('Timeout getting current position')); + delete promiseMap.current[key]; + } + }, 5000); + } else { + reject(new Error('Player is not initialized')); + } + }); + }, + getDuration: () => { + return new Promise((resolve, reject) => { + if (nativeRef.current) { + const key = `duration-${instanceId.current}`; + promiseMap.current[key] = { resolve, reject }; + Commands.getDuration(nativeRef.current); + + setTimeout(() => { + if (promiseMap.current[key]) { + reject(new Error('Timeout getting duration')); + delete promiseMap.current[key]; + } + }, 5000); + } else { + reject(new Error('Player is not initialized')); + } + }); + }, + isPlaying: () => { + return new Promise((resolve, reject) => { + if (nativeRef.current) { + const key = `isPlaying-${instanceId.current}`; + promiseMap.current[key] = { resolve, reject }; + Commands.isPlaying(nativeRef.current); + + setTimeout(() => { + if (promiseMap.current[key]) { + reject(new Error('Timeout getting playing state')); + delete promiseMap.current[key]; + } + }, 5000); + } else { + reject(new Error('Player is not initialized')); + } + }); + }, + getPlaybackSpeed: () => { + return new Promise((resolve, reject) => { + if (nativeRef.current) { + const key = `playbackSpeed-${instanceId.current}`; + promiseMap.current[key] = { resolve, reject }; + Commands.getPlaybackSpeed(nativeRef.current); + + setTimeout(() => { + if (promiseMap.current[key]) { + reject(new Error('Timeout getting playback speed')); + delete promiseMap.current[key]; + } + }, 5000); + } else { + reject(new Error('Player is not initialized')); + } + }); + }, + })); + + // Add event handlers to the combined props + const combinedProps = { + ...props, + onCurrentPosition, + onDuration, + onIsPlaying, + onPlaybackSpeed, + }; + + return ; + } +); + +export default TPStreamsPlayer; diff --git a/src/TPStreamsPlayerViewNativeComponent.ts b/src/TPStreamsPlayerViewNativeComponent.ts index fae3410..5bffb6b 100644 --- a/src/TPStreamsPlayerViewNativeComponent.ts +++ b/src/TPStreamsPlayerViewNativeComponent.ts @@ -1,9 +1,53 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; import type { ViewProps } from 'react-native'; +import type { Double, Float } from 'react-native/Libraries/Types/CodegenTypes'; +import type { HostComponent } from 'react-native'; +import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes'; +import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'; -interface NativeProps extends ViewProps { +export interface NativeProps extends ViewProps { videoId?: string; accessToken?: string; + + // Event props for receiving data from native methods + onCurrentPosition?: DirectEventHandler<{ position: Double }>; + onDuration?: DirectEventHandler<{ duration: Double }>; + onIsPlaying?: DirectEventHandler<{ isPlaying: boolean }>; + onPlaybackSpeed?: DirectEventHandler<{ speed: Float }>; +} + +interface TPStreamsPlayerViewCommands { + play: (viewRef: React.ElementRef>) => void; + pause: (viewRef: React.ElementRef>) => void; + seekTo: ( + viewRef: React.ElementRef>, + positionMs: Double + ) => void; + setPlaybackSpeed: ( + viewRef: React.ElementRef>, + speed: Float + ) => void; + getCurrentPosition: ( + viewRef: React.ElementRef> + ) => void; + getDuration: (viewRef: React.ElementRef>) => void; + isPlaying: (viewRef: React.ElementRef>) => void; + getPlaybackSpeed: ( + viewRef: React.ElementRef> + ) => void; } +export const Commands = codegenNativeCommands({ + supportedCommands: [ + 'play', + 'pause', + 'seekTo', + 'setPlaybackSpeed', + 'getCurrentPosition', + 'getDuration', + 'isPlaying', + 'getPlaybackSpeed', + ], +}); + export default codegenNativeComponent('TPStreamsRNPlayerView'); diff --git a/src/index.tsx b/src/index.tsx index 84bbb85..71edd2d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,6 +2,9 @@ import { NativeModules } from 'react-native'; export { default as TPStreamsPlayerView } from './TPStreamsPlayerViewNativeComponent'; export * from './TPStreamsPlayerViewNativeComponent'; +export { default as TPStreamsPlayer } from './TPStreamsPlayer'; +export type { TPStreamsPlayerRef } from './TPStreamsPlayer'; + const TPStreamsModule = NativeModules.TPStreams; export const TPStreams = { From 30ccaf24b8e915b139fc8b8d0942a932f7bd8aab Mon Sep 17 00:00:00 2001 From: Syed Ibrahim Date: Tue, 3 Jun 2025 18:07:11 +0530 Subject: [PATCH 2/2] feat: expose player events in ReactNative --- .../com/tpstreams/TPStreamsRNPlayerView.kt | 102 +++++-- .../tpstreams/TPStreamsRNPlayerViewManager.kt | 94 +++--- example/src/App.tsx | 244 +++++++++++++++- src/TPStreamsPlayer.tsx | 271 ++++++++++-------- src/TPStreamsPlayerViewNativeComponent.ts | 19 +- 5 files changed, 524 insertions(+), 206 deletions(-) diff --git a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt index 8cf115c..9bbc756 100644 --- a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt +++ b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt @@ -4,14 +4,15 @@ import android.util.Log import android.widget.FrameLayout import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactContext -import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.events.RCTEventEmitter import com.tpstreams.player.TPStreamsPlayer import com.tpstreams.player.TPStreamsPlayerView +import androidx.media3.common.Player +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.PlaybackException class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) { - private val playerView: TPStreamsPlayerView = TPStreamsPlayerView(context) private var player: TPStreamsPlayer? = null private val reactContext: ReactContext = context @@ -23,6 +24,31 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) addView(playerView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) } + // Emit React Native events + private fun emitEvent(eventName: String, data: Map) { + val event = Arguments.createMap() + data.forEach { (key, value) -> + when (value) { + is Int -> event.putInt(key, value) + is Double -> event.putDouble(key, value) + is Boolean -> event.putBoolean(key, value) + is String -> event.putString(key, value) + else -> Log.w("TPStreamsRN", "Unsupported type for event data: ${value::class.java}") + } + } + reactContext.getJSModule(RCTEventEmitter::class.java) + .receiveEvent(id, eventName, event) + } + + private fun sendErrorEvent(message: String, code: Int = 0, details: String? = null) { + val event = Arguments.createMap() + event.putString("message", message) + event.putInt("code", code) + details?.let { event.putString("details", it) } + reactContext.getJSModule(RCTEventEmitter::class.java) + .receiveEvent(id, "onError", event) + } + fun setVideoId(videoId: String?) { this.videoId = videoId tryCreatePlayer() @@ -39,70 +65,88 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) try { player = TPStreamsPlayer.create(context, videoId!!, accessToken!!) + + // Add player event listeners + player?.addListener(createPlayerListener()) + playerView.player = player playerView.showController() + + // Send initial events + emitEvent("onPlayerStateChanged", mapOf("playbackState" to 0)) + emitEvent("onIsPlayingChanged", mapOf("isPlaying" to false)) + emitEvent("onPlaybackSpeedChanged", mapOf("speed" to 1.0)) + emitEvent("onIsLoadingChanged", mapOf("isLoading" to false)) } catch (e: Exception) { Log.e("TPStreamsRN", "Error creating player", e) + sendErrorEvent("Error creating player", 1001, e.message) } } + private fun createPlayerListener(): Player.Listener { + return object : Player.Listener { + override fun onPlaybackStateChanged(playbackState: Int) { + emitEvent("onPlayerStateChanged", mapOf("playbackState" to playbackState)) + } + + override fun onIsPlayingChanged(isPlaying: Boolean) { + emitEvent("onIsPlayingChanged", mapOf("isPlaying" to isPlaying)) + } + + override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) { + emitEvent("onPlaybackSpeedChanged", mapOf("speed" to playbackParameters.speed.toDouble())) + } + + override fun onIsLoadingChanged(isLoading: Boolean) { + emitEvent("onIsLoadingChanged", mapOf("isLoading" to isLoading)) + } + + override fun onPlayerError(error: PlaybackException) { + Log.e("TPStreamsRN", "Player error", error) + sendErrorEvent("Playback error", error.errorCode, error.message) + } + } + } + + // Player control methods fun play() { player?.play() } - + fun pause() { player?.pause() } - + fun seekTo(positionMs: Long) { player?.seekTo(positionMs) } - + fun setPlaybackSpeed(speed: Float) { player?.setPlaybackSpeed(speed) } + // Player information methods with event emission fun getCurrentPosition(): Long { val position = player?.currentPosition ?: 0L - - val event = Arguments.createMap() - event.putDouble("position", position.toDouble()) - reactContext.getJSModule(RCTEventEmitter::class.java) - .receiveEvent(id, "onCurrentPosition", event) - + emitEvent("onCurrentPosition", mapOf("position" to position.toDouble())) return position } fun getDuration(): Long { val duration = player?.duration ?: 0L - - val event = Arguments.createMap() - event.putDouble("duration", duration.toDouble()) - reactContext.getJSModule(RCTEventEmitter::class.java) - .receiveEvent(id, "onDuration", event) - + emitEvent("onDuration", mapOf("duration" to duration.toDouble())) return duration } fun isPlaying(): Boolean { val playing = player?.isPlaying ?: false - - val event = Arguments.createMap() - event.putBoolean("isPlaying", playing) - reactContext.getJSModule(RCTEventEmitter::class.java) - .receiveEvent(id, "onIsPlaying", event) - + emitEvent("onIsPlaying", mapOf("isPlaying" to playing)) return playing } fun getPlaybackSpeed(): Float { val speed = player?.playbackParameters?.speed ?: 1.0f - - val event = Arguments.createMap() - event.putDouble("speed", speed.toDouble()) - reactContext.getJSModule(RCTEventEmitter::class.java) - .receiveEvent(id, "onPlaybackSpeed", event) - + emitEvent("onPlaybackSpeed", mapOf("speed" to speed.toDouble())) return speed } diff --git a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt index 18fe9d1..a9b0030 100644 --- a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt +++ b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt @@ -1,6 +1,5 @@ package com.tpstreams -import com.facebook.react.bridge.ReadableMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext @@ -13,77 +12,86 @@ import com.facebook.react.common.MapBuilder @ReactModule(name = TPStreamsRNPlayerViewManager.NAME) class TPStreamsRNPlayerViewManager : SimpleViewManager(), TPStreamsRNPlayerViewManagerInterface { - private val mDelegate: ViewManagerDelegate - - init { - mDelegate = TPStreamsRNPlayerViewManagerDelegate(this) - } - - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } - - override fun getName(): String { - return NAME - } + + companion object { + const val NAME = "TPStreamsRNPlayerView" + + // Event names + private const val EVENT_CURRENT_POSITION = "onCurrentPosition" + private const val EVENT_DURATION = "onDuration" + private const val EVENT_IS_PLAYING = "onIsPlaying" + private const val EVENT_PLAYBACK_SPEED = "onPlaybackSpeed" + private const val EVENT_PLAYER_STATE_CHANGED = "onPlayerStateChanged" + private const val EVENT_IS_PLAYING_CHANGED = "onIsPlayingChanged" + private const val EVENT_PLAYBACK_SPEED_CHANGED = "onPlaybackSpeedChanged" + private const val EVENT_IS_LOADING_CHANGED = "onIsLoadingChanged" + private const val EVENT_ERROR = "onError" + } + + private val mDelegate: ViewManagerDelegate = + TPStreamsRNPlayerViewManagerDelegate(this) + + override fun getDelegate(): ViewManagerDelegate? = mDelegate + + override fun getName(): String = NAME override fun getExportedCustomDirectEventTypeConstants(): Map { return MapBuilder.builder() - .put("onCurrentPosition", MapBuilder.of("registrationName", "onCurrentPosition")) - .put("onDuration", MapBuilder.of("registrationName", "onDuration")) - .put("onIsPlaying", MapBuilder.of("registrationName", "onIsPlaying")) - .put("onPlaybackSpeed", MapBuilder.of("registrationName", "onPlaybackSpeed")) + .put(EVENT_CURRENT_POSITION, MapBuilder.of("registrationName", EVENT_CURRENT_POSITION)) + .put(EVENT_DURATION, MapBuilder.of("registrationName", EVENT_DURATION)) + .put(EVENT_IS_PLAYING, MapBuilder.of("registrationName", EVENT_IS_PLAYING)) + .put(EVENT_PLAYBACK_SPEED, MapBuilder.of("registrationName", EVENT_PLAYBACK_SPEED)) + .put(EVENT_PLAYER_STATE_CHANGED, MapBuilder.of("registrationName", EVENT_PLAYER_STATE_CHANGED)) + .put(EVENT_IS_PLAYING_CHANGED, MapBuilder.of("registrationName", EVENT_IS_PLAYING_CHANGED)) + .put(EVENT_PLAYBACK_SPEED_CHANGED, MapBuilder.of("registrationName", EVENT_PLAYBACK_SPEED_CHANGED)) + .put(EVENT_IS_LOADING_CHANGED, MapBuilder.of("registrationName", EVENT_IS_LOADING_CHANGED)) + .put(EVENT_ERROR, MapBuilder.of("registrationName", EVENT_ERROR)) .build() } - public override fun createViewInstance(context: ThemedReactContext): TPStreamsRNPlayerView { - return TPStreamsRNPlayerView(context) - } + public override fun createViewInstance(context: ThemedReactContext): TPStreamsRNPlayerView = + TPStreamsRNPlayerView(context) @ReactProp(name = "videoId") override fun setVideoId(view: TPStreamsRNPlayerView, videoId: String?) { - view.setVideoId(videoId) + view.setVideoId(videoId) } @ReactProp(name = "accessToken") override fun setAccessToken(view: TPStreamsRNPlayerView, accessToken: String?) { - view.setAccessToken(accessToken) + view.setAccessToken(accessToken) } + // Command implementations override fun play(view: TPStreamsRNPlayerView) { - view.play() + view.play() } - + override fun pause(view: TPStreamsRNPlayerView) { - view.pause() + view.pause() } - + override fun seekTo(view: TPStreamsRNPlayerView, positionMs: Double) { - view.seekTo(positionMs.toLong()) + view.seekTo(positionMs.toLong()) } - + override fun setPlaybackSpeed(view: TPStreamsRNPlayerView, speed: Float) { - view.setPlaybackSpeed(speed) + view.setPlaybackSpeed(speed) } - + override fun getCurrentPosition(view: TPStreamsRNPlayerView) { - view.getCurrentPosition() + view.getCurrentPosition() } - + override fun getDuration(view: TPStreamsRNPlayerView) { - view.getDuration() + view.getDuration() } - + override fun isPlaying(view: TPStreamsRNPlayerView) { - view.isPlaying() + view.isPlaying() } - + override fun getPlaybackSpeed(view: TPStreamsRNPlayerView) { - view.getPlaybackSpeed() - } - - - companion object { - const val NAME = "TPStreamsRNPlayerView" + view.getPlaybackSpeed() } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 69257dc..ed40e80 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,27 +1,245 @@ -import { View, StyleSheet } from 'react-native'; -import { TPStreamsPlayerView } from 'react-native-tpstreams'; +import { useRef, useState } from 'react'; +import { View, StyleSheet, Button, Text, ScrollView } from 'react-native'; +import { TPStreamsPlayer } from 'react-native-tpstreams'; +import type { TPStreamsPlayerRef } from 'react-native-tpstreams'; export default function App() { + const playerRef = useRef(null); + const [lastError, setLastError] = useState(null); + + const handlePlay = () => { + playerRef.current?.play(); + console.log('Play called'); + }; + + const handlePause = () => { + playerRef.current?.pause(); + console.log('Pause called'); + }; + + const handleSeek = () => { + playerRef.current?.seekTo(30000); + console.log('Seek to 30s called'); + }; + + const handleSpeedNormal = () => { + playerRef.current?.setPlaybackSpeed(1.0); + console.log('Speed set to 1.0x'); + }; + + const handleSpeedFast = () => { + playerRef.current?.setPlaybackSpeed(2.0); + console.log('Speed set to 2.0x'); + }; + + // Get current position with the new Promise-based approach + const checkCurrentPosition = async () => { + try { + const position = await playerRef.current?.getCurrentPosition(); + console.log(`Current position: ${position}ms`); + } catch (error) { + console.error('Error getting position:', error); + } + }; + + // Get duration with the new Promise-based approach + const checkDuration = async () => { + try { + const duration = await playerRef.current?.getDuration(); + console.log(`Duration: ${duration}ms`); + } catch (error) { + console.error('Error getting duration:', error); + } + }; + + // Get playing status with the new Promise-based approach + const checkIsPlaying = async () => { + try { + const playing = await playerRef.current?.isPlaying(); + console.log(`Is playing: ${playing}`); + } catch (error) { + console.error('Error checking play status:', error); + } + }; + + // Get playback speed with the new Promise-based approach + const checkPlaybackSpeed = async () => { + try { + const speed = await playerRef.current?.getPlaybackSpeed(); + console.log(`Current playback speed: ${speed}`); + } catch (error) { + console.error('Error getting playback speed:', error); + } + }; + + // Event handlers for player events + const handlePlayerStateChanged = (state: number) => { + console.log(`EVENT - Player state changed: ${state}`); + }; + + const handleIsPlayingChanged = (isPlaying: boolean) => { + console.log(`EVENT - Is playing changed: ${isPlaying}`); + }; + + const handlePlaybackSpeedChanged = (speed: number) => { + console.log(`EVENT - Playback speed changed: ${speed}`); + }; + + const handleIsLoadingChanged = (isLoading: boolean) => { + console.log(`EVENT - Is loading changed: ${isLoading}`); + }; + + const handleError = (error: { + message: string; + code: number; + details?: string; + }) => { + const errorMessage = `Error (${error.code}): ${error.message}${error.details ? ` - ${error.details}` : ''}`; + console.error(errorMessage); + setLastError(errorMessage); + }; + return ( - - - + + + + + + + {lastError && ( + + {lastError} + + )} + + + Playback Controls + + +