diff --git a/README.md b/README.md
index a8d1db0..6ad8087 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,8 @@ import { TPStreamsPlayerView } from "react-native-tpstreams";
- `onError(error: {message: string, code: number, details?: string})`: Fires when an error occurs.
+- `onAccessTokenExpired(videoId: string, callback: (newToken: string) => void)`: Fires when the access token expires. Call the callback with a new token to continue playback.
+
---
## Player Props
@@ -173,6 +175,11 @@ function TPStreamsPlayerExample() {
onPlaybackSpeedChanged={(speed) => console.log(`Speed changed: ${speed}x`)}
onIsLoadingChanged={(isLoading) => console.log(`Loading: ${isLoading}`)}
onError={(error) => console.error('Player error:', error)}
+ onAccessTokenExpired={async (videoId, callback) => {
+ // Fetch a new token from your server
+ const newToken = await getNewTokenForVideo(videoId);
+ callback(newToken);
+ }}
/>
diff --git a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt
index bd938ee..fc1d443 100644
--- a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt
+++ b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt
@@ -23,6 +23,7 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
private var startAt: Long = 0
private var showDefaultCaptions: Boolean = false
private var enableDownload: Boolean = false
+ private var accessTokenCallback: ((String) -> Unit)? = null
init {
addView(playerView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
@@ -76,6 +77,14 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
fun setEnableDownload(enableDownload: Boolean) {
this.enableDownload = enableDownload
}
+
+ fun setNewAccessToken(newToken: String) {
+ Log.d("TPStreamsRNPlayerView", "Setting new access token")
+ accessTokenCallback?.let { callback ->
+ callback(newToken)
+ accessTokenCallback = null
+ } ?: Log.w("TPStreamsRNPlayerView", "No callback available for token refresh")
+ }
fun tryCreatePlayer() {
if (videoId.isNullOrEmpty() || accessToken.isNullOrEmpty()) return
@@ -92,6 +101,17 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
showDefaultCaptions
)
+ player?.listener = object : TPStreamsPlayer.Listener {
+ override fun onAccessTokenExpired(videoId: String, callback: (String) -> Unit) {
+ if (accessTokenCallback != null) {
+ Log.w("TPStreamsRNPlayerView", "onAccessTokenExpired called while another refresh is in progress. Ignoring.")
+ return
+ }
+ accessTokenCallback = callback
+ emitEvent("onAccessTokenExpired", mapOf("videoId" to videoId))
+ }
+ }
+
// Add player event listeners
player?.addListener(createPlayerListener())
@@ -188,5 +208,6 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
Log.e("TPStreamsRN", "Error releasing player", e)
}
player = null
+ accessTokenCallback = null
}
}
diff --git a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt
index 9495062..6768105 100644
--- a/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt
+++ b/android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt
@@ -26,6 +26,7 @@ class TPStreamsRNPlayerViewManager : SimpleViewManager(),
private const val EVENT_PLAYBACK_SPEED_CHANGED = "onPlaybackSpeedChanged"
private const val EVENT_IS_LOADING_CHANGED = "onIsLoadingChanged"
private const val EVENT_ERROR = "onError"
+ private const val EVENT_ACCESS_TOKEN_EXPIRED = "onAccessTokenExpired"
}
private val mDelegate: ViewManagerDelegate =
@@ -46,6 +47,7 @@ class TPStreamsRNPlayerViewManager : SimpleViewManager(),
.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))
+ .put(EVENT_ACCESS_TOKEN_EXPIRED, MapBuilder.of("registrationName", EVENT_ACCESS_TOKEN_EXPIRED))
.build()
}
@@ -115,6 +117,10 @@ class TPStreamsRNPlayerViewManager : SimpleViewManager(),
view.getPlaybackSpeed()
}
+ override fun setNewAccessToken(view: TPStreamsRNPlayerView, newToken: String) {
+ view.setNewAccessToken(newToken)
+ }
+
override fun onAfterUpdateTransaction(view: TPStreamsRNPlayerView) {
super.onAfterUpdateTransaction(view)
view.tryCreatePlayer()
diff --git a/src/TPStreamsPlayer.tsx b/src/TPStreamsPlayer.tsx
index c09ef6f..d6c7523 100644
--- a/src/TPStreamsPlayer.tsx
+++ b/src/TPStreamsPlayer.tsx
@@ -48,6 +48,10 @@ export interface TPStreamsPlayerProps extends ViewProps {
code: number;
details?: string;
}) => void;
+ onAccessTokenExpired?: (
+ videoId: string,
+ callback: (newToken: string) => void
+ ) => void;
}
/**
@@ -71,6 +75,7 @@ const TPStreamsPlayerView = forwardRef<
onPlaybackSpeedChanged,
onIsLoadingChanged,
onError,
+ onAccessTokenExpired,
...restProps
} = props;
@@ -160,6 +165,22 @@ const TPStreamsPlayerView = forwardRef<
[onError]
);
+ const handleAccessTokenExpired = useCallback(
+ (event: { nativeEvent: { videoId: string } }) => {
+ if (onAccessTokenExpired) {
+ const { videoId: expiredVideoId } = event.nativeEvent;
+ onAccessTokenExpired(expiredVideoId, (newToken: string) => {
+ if (nativeRef.current) {
+ Commands.setNewAccessToken(nativeRef.current, newToken);
+ } else {
+ console.error('[RN] Native ref is not available');
+ }
+ });
+ }
+ },
+ [onAccessTokenExpired]
+ );
+
// Helper to create promise-based API methods
const createPromiseMethod = useCallback(
(command: (ref: any) => void, eventKey: string) => {
@@ -225,6 +246,7 @@ const TPStreamsPlayerView = forwardRef<
onPlaybackSpeedChanged: handlePlaybackSpeedChanged,
onIsLoadingChanged: handleIsLoadingChanged,
onError: handleError,
+ onAccessTokenExpired: handleAccessTokenExpired,
};
return ;
diff --git a/src/TPStreamsPlayerViewNativeComponent.ts b/src/TPStreamsPlayerViewNativeComponent.ts
index 7831a28..538f035 100644
--- a/src/TPStreamsPlayerViewNativeComponent.ts
+++ b/src/TPStreamsPlayerViewNativeComponent.ts
@@ -35,6 +35,7 @@ export interface NativeProps extends ViewProps {
onPlaybackSpeedChanged?: DirectEventHandler<{ speed: Double }>;
onIsLoadingChanged?: DirectEventHandler<{ isLoading: boolean }>;
onError?: DirectEventHandler;
+ onAccessTokenExpired?: DirectEventHandler<{ videoId: string }>;
}
interface TPStreamsPlayerViewCommands {
@@ -56,6 +57,10 @@ interface TPStreamsPlayerViewCommands {
getPlaybackSpeed: (
viewRef: React.ElementRef>
) => void;
+ setNewAccessToken: (
+ viewRef: React.ElementRef>,
+ newToken: string
+ ) => void;
}
export const Commands = codegenNativeCommands({
@@ -68,6 +73,7 @@ export const Commands = codegenNativeCommands({
'getDuration',
'isPlaying',
'getPlaybackSpeed',
+ 'setNewAccessToken',
],
});