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

## [Unreleased]

### Added

- Added `contentprotectionerror` player event on Web, Android, and iOS platforms.

## [11.4.0] - 26-07-06

### Fixed
Expand Down
12 changes: 12 additions & 0 deletions android/src/main/java/com/theoplayer/PlayerEventEmitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ private const val EVENT_THEOLIVE_EVENT = "onNativeTHEOliveEvent"
private const val EVENT_THEOADS_EVENT = "onNativeTHEOadsEvent"
private const val EVENT_PRESENTATIONMODECHANGE = "onNativePresentationModeChange"
private const val EVENT_VOLUMECHANGE = "onNativeVolumeChange"
private const val EVENT_CONTENTPROTECTIONERROR = "onNativeContentProtectionError"
private const val EVENT_DIMENSIONCHANGE = "onNativeDimensionChange"
private const val EVENT_VIDEORESIZE = "onNativeVideoResize"

Expand Down Expand Up @@ -132,6 +133,7 @@ class PlayerEventEmitter internal constructor(
EVENT_THEOADS_EVENT,
EVENT_PRESENTATIONMODECHANGE,
EVENT_VOLUMECHANGE,
EVENT_CONTENTPROTECTIONERROR,
EVENT_DIMENSIONCHANGE,
EVENT_VIDEORESIZE
)
Expand Down Expand Up @@ -171,6 +173,7 @@ class PlayerEventEmitter internal constructor(
EVENT_THEOADS_EVENT,
EVENT_PRESENTATIONMODECHANGE,
EVENT_VOLUMECHANGE,
EVENT_CONTENTPROTECTIONERROR,
EVENT_DIMENSIONCHANGE,
EVENT_VIDEORESIZE
)
Expand Down Expand Up @@ -246,6 +249,8 @@ class PlayerEventEmitter internal constructor(
EventListener { event: PresentationModeChange -> onPresentationModeChange(event) }
playerListeners[PlayerEventTypes.VOLUMECHANGE] =
EventListener { event: VolumeChangeEvent -> onVolumeChange(event) }
playerListeners[PlayerEventTypes.CONTENTPROTECTIONERROR] =
EventListener { event: ContentProtectionErrorEvent -> onContentProtectionError(event) }
playerListeners[PlayerEventTypes.RESIZE] =
EventListener { event: ResizeEvent -> onResize(event) }
textTrackListeners[TextTrackListEventTypes.ADDTRACK] =
Expand Down Expand Up @@ -479,6 +484,13 @@ class PlayerEventEmitter internal constructor(
)
}

private fun onContentProtectionError(event: ContentProtectionErrorEvent) {
receiveEvent(
EVENT_CONTENTPROTECTIONERROR,
PayloadBuilder().error(event.errorObject.code.id.toString(), event.errorObject.message).build()
)
}

private fun onResize(event: ResizeEvent) {
receiveEvent(
EVENT_VIDEORESIZE,
Expand Down
1 change: 1 addition & 0 deletions ios/THEOplayerRCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ @interface RCT_EXTERN_MODULE(THEOplayerRCTViewManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(onNativeDeviceOrientationChanged, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativePlayerReady, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativePresentationModeChange, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativeContentProtectionError, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativeDimensionChange, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativeVideoResize, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onNativeAdEvent, RCTDirectEventBlock);
Expand Down
1 change: 1 addition & 0 deletions ios/THEOplayerRCTMainEventHandler.swift
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class THEOplayerRCTMainEventHandler {
var onNativeRateChange: RCTDirectEventBlock?
var onNativeWaiting: RCTDirectEventBlock?
var onNativeCanPlay: RCTDirectEventBlock?
var onNativeContentProtectionError: RCTDirectEventBlock?
Comment thread
wvanhaevre marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 iOS callback plumbing added but no native listener attached (deferred)

The iOS changes add the bridge property (ios/THEOplayerRCTBridge.m:50), the handler property (ios/THEOplayerRCTMainEventHandler.swift:33), and the setter (ios/THEOplayerRCTView.swift:473-477), but no player.addEventListener for content-protection errors is registered in attachListeners(). This matches the PR's stated intent that the iOS native listener is deferred until the SDK exposes the event type, so this is intentional and not a runtime bug (the prop is set but simply never invoked). Reviewer should confirm a follow-up ticket exists to wire the actual listener once the iOS SDK supports it.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

var onNativeDimensionChange: RCTDirectEventBlock?
var onNativeVideoResize: RCTDirectEventBlock?

Expand Down
6 changes: 6 additions & 0 deletions ios/THEOplayerRCTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ public class THEOplayerRCTView: UIView {
if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativeCanPlay prop set.") }
}

@objc(setOnNativeContentProtectionError:)
func setOnNativeContentProtectionError(nativeContentProtectionError: @escaping RCTDirectEventBlock) {
self.mainEventHandler.onNativeContentProtectionError = nativeContentProtectionError
if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativeContentProtectionError prop set.") }
}

@objc(setOnNativeDimensionChange:)
func setOnNativeDimensionChange(nativeDimensionChange: @escaping RCTDirectEventBlock) {
self.mainEventHandler.onNativeDimensionChange = nativeDimensionChange
Expand Down
58 changes: 58 additions & 0 deletions src/api/error/ContentProtectionError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* An error related to content protection.
*
* @category Errors
* @category Content Protection
* @public
*/
export interface ContentProtectionErrorObject {
/**
* The error code.
*/
readonly errorCode: string;

/**
* The error message.
*/
readonly errorMessage: string;

/**
* The URL that was used in the request.
*
* @remarks
* Only available for certificate or license request errors.
*/
readonly url?: string;

/**
* The status code from the HTTP response.
*
* @remarks
* Only available for certificate or license request errors.
*/
readonly status?: number;

/**
* The status text from the HTTP response.
*
* @remarks
* Only available for certificate or license request errors.
*/
readonly statusText?: string;

/**
* The body contained in the HTTP response.
*
* @remarks
* Only available for certificate or license request errors.
*/
readonly response?: string;

/**
* The internal error code from the CDM.
*
* @remarks
* Only available for internal CDM errors.
*/
readonly systemCode?: number;
}
1 change: 1 addition & 0 deletions src/api/error/barrel.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './PlayerError';
export * from './ChromecastError';
export * from './ContentProtectionError';
17 changes: 16 additions & 1 deletion src/api/event/PlayerEvent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MediaTrack, PlayerError, PlayerEventType, PresentationMode, TextTrack, TypedSource } from 'react-native-theoplayer';
import type { MediaTrack, PlayerError, PlayerEventType, PresentationMode, TextTrack, TypedSource, ContentProtectionErrorObject } from 'react-native-theoplayer';
import type { TimeRange } from '../timeranges/TimeRange';
import type { Event } from './Event';

Expand Down Expand Up @@ -297,3 +297,18 @@ export interface SeekedEvent extends Event<PlayerEventType.SEEKED> {
*/
readonly currentTime: number;
}

/**
* Dispatched when an error related to content protection occurs.
*
* @category Events
* @category Player
* @category Content Protection
* @public
*/
export interface ContentProtectionErrorEvent extends Event<PlayerEventType.CONTENT_PROTECTION_ERROR> {
/**
* An error object containing additional information about the content protection error.
*/
readonly error: ContentProtectionErrorObject;
}
7 changes: 7 additions & 0 deletions src/api/player/PlayerEventMap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Event } from '../event/Event';
import type {
ContentProtectionErrorEvent,
CurrentSourceChangeEvent,
DimensionChangeEvent,
DurationChangeEvent,
Expand Down Expand Up @@ -63,6 +64,7 @@ export enum PlayerEventType {
DESTROY = 'destroy',
DIMENSION_CHANGE = 'dimensionchange',
VIDEO_RESIZE = 'videoresize',
CONTENT_PROTECTION_ERROR = 'contentprotectionerror',
}

/**
Expand Down Expand Up @@ -242,4 +244,9 @@ export interface PlayerEventMap {
* Dispatched when the video size changes.
*/
[PlayerEventType.VIDEO_RESIZE]: VideoResizeEvent;

/**
* Dispatched when an error related to content protection occurs.
*/
[PlayerEventType.CONTENT_PROTECTION_ERROR]: ContentProtectionErrorEvent;
}
8 changes: 8 additions & 0 deletions src/internal/THEOplayerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
DefaultAirplayStateChangeEvent,
DefaultChromecastChangeEvent,
DefaultChromecastErrorEvent,
DefaultContentProtectionErrorEvent,
DefaultCurrentSourceChangeEvent,
DefaultDimensionChangeEvent,
DefaultDurationChangeEvent,
Expand Down Expand Up @@ -52,6 +53,7 @@ import type {
} from './adapter/event/native/NativeTrackEvent';
import { toMediaTrackType, toMediaTrackTypeEventType, toTextTrackEventType, toTrackListEventType } from './adapter/event/native/NativeTrackEvent';
import {
NativeContentProtectionErrorEvent,
NativeCurrentSourceChangeEvent,
NativeDurationChangeEvent,
NativeErrorEvent,
Expand Down Expand Up @@ -116,6 +118,7 @@ interface THEOplayerRCTViewProps {
onNativeCastEvent: (event: NativeSyntheticEvent<NativeCastEvent>) => void;
onNativePresentationModeChange: (event: NativeSyntheticEvent<NativePresentationModeChangeEvent>) => void;
onNativeDeviceOrientationChanged: () => void;
onNativeContentProtectionError: (event: NativeSyntheticEvent<NativeContentProtectionErrorEvent>) => void;
onNativeDimensionChange: (event: NativeSyntheticEvent<NativeDimensionChangeEvent>) => void;
onNativeVideoResize: (event: NativeSyntheticEvent<NativeVideoResizeEvent>) => void;
}
Expand Down Expand Up @@ -422,6 +425,10 @@ export class THEOplayerView extends PureComponent<React.PropsWithChildren<THEOpl
this._facade?.dispatchEvent(new DefaultDimensionChangeEvent(width, height));
};

private _onContentProtectionError = (event: NativeSyntheticEvent<NativeContentProtectionErrorEvent>) => {
this._facade?.dispatchEvent(new DefaultContentProtectionErrorEvent(event.nativeEvent.error));
};

private _onVideoResize = (event: NativeSyntheticEvent<NativeVideoResizeEvent>) => {
this._facade?.dispatchEvent(new DefaultVideoResizeEvent(event.nativeEvent.videoWidth, event.nativeEvent.videoHeight));
};
Expand Down Expand Up @@ -503,6 +510,7 @@ export class THEOplayerView extends PureComponent<React.PropsWithChildren<THEOpl
onNativeCastEvent={this._onCastEvent}
onNativePresentationModeChange={this._onPresentationModeChange}
onNativeDeviceOrientationChanged={this._onDeviceOrientationChanged}
onNativeContentProtectionError={this._onContentProtectionError}
onNativeDimensionChange={this._onDimensionChange}
onNativeVideoResize={this._onVideoResize}
/>
Expand Down
19 changes: 19 additions & 0 deletions src/internal/adapter/WebEventForwarder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
CastStateChangeEvent,
ChromecastErrorEvent,
ChromelessPlayer,
ContentProtectionErrorEvent as NativeContentProtectionErrorEvent,
CurrentSourceChangeEvent as NativeCurrentSourceChangeEvent,
DimensionChangeEvent as NativeDimensionChangeEvent,
DurationChangeEvent as NativeDurationChangeEvent,
Expand Down Expand Up @@ -52,6 +53,7 @@ import {
DefaultAirplayStateChangeEvent,
DefaultChromecastChangeEvent,
DefaultChromecastErrorEvent,
DefaultContentProtectionErrorEvent,
DefaultCurrentSourceChangeEvent,
DefaultDimensionChangeEvent,
DefaultDurationChangeEvent,
Expand Down Expand Up @@ -112,6 +114,7 @@ export class WebEventForwarder {
this._player.addEventListener('ratechange', this.onPlaybackRateChange);
this._player.addEventListener('segmentnotfound', this.onSegmentNotFound);
this._player.addEventListener('volumechange', this.onVolumeChangeEvent);
this._player.addEventListener('contentprotectionerror', this.onContentProtectionError);
this._player.addEventListener('dimensionchange', this.onDimensionChange);
this._player.addEventListener('resize', this.onVideoResize);

Expand Down Expand Up @@ -163,6 +166,7 @@ export class WebEventForwarder {
this._player.removeEventListener('ratechange', this.onPlaybackRateChange);
this._player.removeEventListener('segmentnotfound', this.onSegmentNotFound);
this._player.removeEventListener('volumechange', this.onVolumeChangeEvent);
this._player.removeEventListener('contentprotectionerror', this.onContentProtectionError);
this._player.removeEventListener('dimensionchange', this.onDimensionChange);
this._player.removeEventListener('resize', this.onVideoResize);

Expand Down Expand Up @@ -227,6 +231,21 @@ export class WebEventForwarder {
);
};

private readonly onContentProtectionError = (event: NativeContentProtectionErrorEvent) => {
const { code, message: errorMessage, url, status, statusText, response, systemCode } = event.errorObject;
this._facade.dispatchEvent(
new DefaultContentProtectionErrorEvent({
errorCode: code.toString(),
errorMessage,
url,
status,
statusText,
response,
systemCode,
}),
);
};

private readonly onProgress = () => {
this._facade.dispatchEvent(new DefaultProgressEvent(fromTimeRanges(this._player.seekable), fromTimeRanges(this._player.buffered)));
};
Expand Down
8 changes: 8 additions & 0 deletions src/internal/adapter/event/PlayerEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
ChromecastChangeEvent,
ChromecastError,
ChromecastErrorEvent,
ContentProtectionErrorEvent,
ContentProtectionErrorObject,
CurrentSourceChangeEvent,
DimensionChangeEvent,
DurationChangeEvent,
Expand Down Expand Up @@ -314,3 +316,9 @@ export class DefaultChromecastErrorEvent extends BaseEvent<PlayerEventType.CAST_
this.subType = CastEventType.CHROMECAST_ERROR;
}
}

export class DefaultContentProtectionErrorEvent extends BaseEvent<PlayerEventType.CONTENT_PROTECTION_ERROR> implements ContentProtectionErrorEvent {
constructor(public readonly error: ContentProtectionErrorObject) {
super(PlayerEventType.CONTENT_PROTECTION_ERROR);
}
}
5 changes: 5 additions & 0 deletions src/internal/adapter/event/native/NativePlayerEvent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
ContentProtectionErrorObject,
MediaTrack,
PlayerError,
PlayerVersion,
Expand Down Expand Up @@ -162,6 +163,10 @@ export interface NativeSegmentNotFoundEvent {
readonly retryCount: number;
}

export interface NativeContentProtectionErrorEvent {
error: ContentProtectionErrorObject;
}

export interface NativePlayerStateEvent {
readonly version: PlayerVersion;

Expand Down
Loading