Skip to content

Commit

Permalink
Merge pull request #406 from bitmovin/feature/support-cue-enter-and-e…
Browse files Browse the repository at this point in the history
…xit-events

Support `CueEnter` and `CueExit` events
  • Loading branch information
123mpozzi authored Mar 6, 2024
2 parents 0aa1e4c + f60c8e6 commit 941eec6
Show file tree
Hide file tree
Showing 16 changed files with 163 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Added

- `CueEnterEvent` and `CueExitEvent` to signal when a subtitle entry transitions into an active or inactive status respectively

## [0.18.0] (2024-03-06)

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ private val EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING = mapOf(
PlayerEvent.CastStopped::class to "castStopped",
PlayerEvent.CastWaitingForDevice::class to "castWaitingForDevice",
PlayerEvent.CastTimeUpdated::class to "castTimeUpdated",
PlayerEvent.CueEnter::class to "cueEnter",
PlayerEvent.CueExit::class to "cueExit",
)

private val EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING_UI = mapOf<KClass<out Event>, String>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
"castStopped" to "onCastStopped",
"castWaitingForDevice" to "onCastWaitingForDevice",
"castTimeUpdated" to "onCastTimeUpdated",
"cueEnter" to "onCueEnter",
"cueExit" to "onCueExit",
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,18 @@ fun PlayerEvent.toJson(): WritableMap {
json.putString("deviceName", deviceName)
}

is PlayerEvent.CueEnter -> {
json.putDouble("start", start)
json.putDouble("end", end)
json.putString("text", text)
}

is PlayerEvent.CueExit -> {
json.putDouble("start", start)
json.putDouble("end", end)
json.putString("text", text)
}

else -> {
// Event is not supported yet or does not have any additional data
}
Expand Down
2 changes: 2 additions & 0 deletions example/src/screens/SubtitlePlayback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export default function SubtitlePlayback() {
<PlayerView
player={player}
style={styles.player}
onCueEnter={onEvent}
onCueExit={onEvent}
onSubtitleAdded={onEvent}
onSubtitleChanged={onEvent}
onSubtitleRemoved={onEvent}
Expand Down
2 changes: 2 additions & 0 deletions integration_test/playertesting/EventType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export enum EventType {
CastStopped = 'onCastStopped',
CastTimeUpdated = 'onCastTimeUpdated',
CastWaitingForDevice = 'onCastWaitingForDevice',
CueEnter = 'onCueEnter',
CueExit = 'onCueExit',
Destroy = 'onDestroy',
Event = 'onEvent',
FullscreenEnabled = 'onFullscreenEnabled',
Expand Down
40 changes: 40 additions & 0 deletions integration_test/tests/captionTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TestScope } from 'cavy';
import {
callPlayer,
callPlayerAndExpectEvent,
EventSequence,
EventType,
expectEvents,
loadSourceConfig,
startPlayerTest,
} from '../playertesting';
import { Sources } from './helper/Sources';

export default (spec: TestScope) => {
spec.describe('playing captions', () => {
spec.it('emits CueEnter and CueExit events', async () => {
await startPlayerTest({}, async () => {
await loadSourceConfig(Sources.sintel);
await callPlayer(async (player) => {
const subtitleTrack = (await player.getAvailableSubtitles())[1];
player.setSubtitleTrack(subtitleTrack.identifier);
player.play();
});
await callPlayerAndExpectEvent((player) => {
player.seek(105);
}, EventType.Seeked);
await expectEvents(
EventSequence(
EventType.CueEnter,
EventType.CueExit,
EventType.CueEnter,
EventType.CueExit,
EventType.CueEnter,
EventType.CueExit
),
30
);
});
});
});
};
5 changes: 5 additions & 0 deletions integration_test/tests/helper/Sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ export const Sources = {
url: 'https://cph-msl.akamaized.net/hls/live/2000341/test/master.m3u8',
type: SourceType.HLS,
} as SourceConfig,

sintel: {
url: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8',
type: SourceType.HLS,
} as SourceConfig,
};
9 changes: 8 additions & 1 deletion integration_test/tests/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import AdvertisingTest from './advertisingTest';
import CaptionTest from './captionTest';
import LoadingTest from './loadingTest';
import PlaybackTest from './playbackTest';
import UnloadingTest from './unloadingTest';

export default [AdvertisingTest, LoadingTest, PlaybackTest, UnloadingTest];
export default [
AdvertisingTest,
CaptionTest,
LoadingTest,
PlaybackTest,
UnloadingTest,
];
24 changes: 24 additions & 0 deletions ios/Event+JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,30 @@ extension PlaybackSpeedChangedEvent: JsonConvertible {
}
}

extension CueEnterEvent: JsonConvertible {
func toJSON() -> [AnyHashable: Any] {
toEventJSON {
[
"start": startTime,
"end": endTime,
"text": text
]
}
}
}

extension CueExitEvent: JsonConvertible {
func toJSON() -> [AnyHashable: Any] {
toEventJSON {
[
"start": startTime,
"end": endTime,
"text": text
]
}
}
}

extension PlayerActiveEvent: DefaultJsonConvertibleEvent {}
extension DestroyEvent: DefaultJsonConvertibleEvent {}
extension MutedEvent: DefaultJsonConvertibleEvent {}
Expand Down
8 changes: 8 additions & 0 deletions ios/RNPlayerView+PlayerListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ extension RNPlayerView: PlayerListener {
onVideoPlaybackQualityChanged?(event.toJSON())
}

public func onCueEnter(_ event: CueEnterEvent, player: Player) {
onCueEnter?(event.toJSON())
}

public func onCueExit(_ event: CueExitEvent, player: Player) {
onCueExit?(event.toJSON())
}

#if os(iOS)
public func onCastAvailable(_ event: CastAvailableEvent, player: Player) {
onCastAvailable?(event.toJSON())
Expand Down
2 changes: 2 additions & 0 deletions ios/RNPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class RNPlayerView: UIView {
@objc var onCastWaitingForDevice: RCTBubblingEventBlock?
@objc var onPictureInPictureAvailabilityChanged: RCTBubblingEventBlock?
@objc var onPlaybackSpeedChanged: RCTBubblingEventBlock?
@objc var onCueEnter: RCTBubblingEventBlock?
@objc var onCueExit: RCTBubblingEventBlock?
@objc var config: [String: Any]?

/// The `PlayerView` subview.
Expand Down
2 changes: 2 additions & 0 deletions ios/RNPlayerViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ @interface RCT_EXTERN_REMAP_MODULE(NativePlayerView, RNPlayerViewManager, RCTVie
RCT_EXPORT_VIEW_PROPERTY(onCastWaitingForDevice, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureAvailabilityChanged, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPlaybackSpeedChanged, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onCueEnter, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onCueExit, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary)

RCT_EXTERN_METHOD(attachPlayer:(nonnull NSNumber *)viewId playerId:(NSString *)playerId playerConfig:(nullable NSDictionary *)playerConfig)
Expand Down
10 changes: 10 additions & 0 deletions src/components/PlayerView/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import {
DownloadFinishedEvent,
VideoDownloadQualityChangedEvent,
PlaybackSpeedChangedEvent,
CueEnterEvent,
CueExitEvent,
} from '../../events';

/**
Expand Down Expand Up @@ -169,6 +171,14 @@ interface EventProps {
* @platform iOS, Android
*/
onCastWaitingForDevice: CastWaitingForDeviceEvent;
/**
* Event emitted when a subtitle entry transitions into the active status.
*/
onCueEnter: CueEnterEvent;
/**
* Event emitted when an active subtitle entry transitions into the inactive status.
*/
onCueExit: CueExitEvent;
/**
* Event emitted when the player is destroyed.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/components/PlayerView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ export function PlayerView({
onCastStopped={proxy(props.onCastStopped)}
onCastTimeUpdated={proxy(props.onCastTimeUpdated)}
onCastWaitingForDevice={proxy(props.onCastWaitingForDevice)}
onCueEnter={proxy(props.onCueEnter)}
onCueExit={proxy(props.onCueExit)}
onDestroy={proxy(props.onDestroy)}
onEvent={proxy(props.onEvent)}
onFullscreenEnabled={proxy(props.onFullscreenEnabled)}
Expand Down
36 changes: 36 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,3 +735,39 @@ export interface PlaybackSpeedChangedEvent extends Event {
*/
to: number;
}

/**
* Emitted when a subtitle entry transitions into the active status.
*/
export interface CueEnterEvent extends Event {
/**
* The playback time in seconds when the subtitle should be rendered.
*/
start: number;
/**
* The playback time in seconds when the subtitle should be hidden.
*/
end: number;
/**
* The textual content of this subtitle.
*/
text?: string;
}

/**
* Emitted when an active subtitle entry transitions into the inactive status.
*/
export interface CueExitEvent extends Event {
/**
* The playback time in seconds when the subtitle should be rendered.
*/
start: number;
/**
* The playback time in seconds when the subtitle should be hidden.
*/
end: number;
/**
* The textual content of this subtitle.
*/
text?: string;
}

0 comments on commit 941eec6

Please sign in to comment.