Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add ios support for accessing WebVTT Subtitle Content #3541

Merged
19 changes: 18 additions & 1 deletion docs/pages/component/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,24 @@ Example:
}
```

Platforms: Android
### `onTextTrackDataChanged`
Callback function that is called when new subtitle data is available. It provides the actual subtitle content for the current selected text track, if available (mainly WebVTT).

Payload:

Property | Type | Description
--- | --- | ---
`subtitleTracks` | `string` | The subtitles text content in a compatible format.


Example:
```javascript
{
subtitleTracks: "This blade has a dark past.",
coofzilla marked this conversation as resolved.
Show resolved Hide resolved
}
```

Platforms: iOS

### `onVideoTracks`
Callback function that is called when video tracks change
Expand Down
16 changes: 14 additions & 2 deletions examples/basic/src/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import Video, {
ResizeMode,
SelectedTrack,
DRMType,
OnTextTrackDataChangedData,
SelectedTrackType,
} from 'react-native-video';
import ToggleControl from './ToggleControl';
import MultiValueControl, {
Expand Down Expand Up @@ -120,7 +122,12 @@ class VideoPlayer extends Component {
},
];

srcIosList = [];
srcIosList = [
{
description: 'sintel with subtitles',
uri: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8',
},
];

srcAndroidList = [
{
Expand Down Expand Up @@ -231,7 +238,7 @@ class VideoPlayer extends Component {

onTextTracks = (data: OnTextTracksData) => {
const selectedTrack = data.textTracks?.find((x: TextTrack) => {
return x.selected;
return x?.selected;
coofzilla marked this conversation as resolved.
Show resolved Hide resolved
});

this.setState({
Expand All @@ -248,6 +255,10 @@ class VideoPlayer extends Component {
}
};

onTextTrackDataChanged = (data: OnTextTrackDataChangedData) => {
console.log(`Subtitles: ${JSON.stringify(data, null, 2)}`);
};

onAspectRatio = (data: OnVideoAspectRatioData) => {
console.log('onAspectRadio called ' + JSON.stringify(data));
this.setState({
Expand Down Expand Up @@ -749,6 +760,7 @@ class VideoPlayer extends Component {
onLoad={this.onLoad}
onAudioTracks={this.onAudioTracks}
onTextTracks={this.onTextTracks}
onTextTrackDataChanged={this.onTextTrackDataChanged}
onProgress={this.onProgress}
onEnd={this.onEnd}
progressUpdateInterval={1000}
Expand Down
14 changes: 13 additions & 1 deletion ios/Video/Features/RCTPlayerObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ protocol RCTPlayerObserverHandler: RCTPlayerObserverHandlerObjc {
func handleExternalPlaybackActiveChange(player: AVPlayer, change: NSKeyValueObservedChange<Bool>)
func handleViewControllerOverlayViewFrameChange(overlayView: UIView, change: NSKeyValueObservedChange<CGRect>)
func handleTracksChange(playerItem: AVPlayerItem, change: NSKeyValueObservedChange<[AVPlayerItemTrack]>)
func handleLegibleOutput(strings: [NSAttributedString])
}

// MARK: - RCTPlayerObserver

class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate {
class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVPlayerItemLegibleOutputPushDelegate {
weak var _handlers: RCTPlayerObserverHandler?

var player: AVPlayer? {
Expand All @@ -57,8 +58,11 @@ class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate {

// handle timedMetadata
let metadataOutput = AVPlayerItemMetadataOutput()
let legibleOutput = AVPlayerItemLegibleOutput()
playerItem.add(metadataOutput)
playerItem.add(legibleOutput)
metadataOutput.setDelegate(self, queue: .main)
legibleOutput.setDelegate(self, queue: .main)
}
}

Expand Down Expand Up @@ -113,6 +117,14 @@ class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate {
}
}

func legibleOutput(_: AVPlayerItemLegibleOutput,
didOutputAttributedStrings strings: [NSAttributedString],
nativeSampleBuffers _: [Any],
forItemTime _: CMTime) {
guard let _handlers else { return }
_handlers.handleLegibleOutput(strings: strings)
}

func addPlayerObservers() {
guard let player, let _handlers else {
return
Expand Down
7 changes: 7 additions & 0 deletions ios/Video/RCTVideo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
@objc var onReceiveAdEvent: RCTDirectEventBlock?
@objc var onTextTracks: RCTDirectEventBlock?
@objc var onAudioTracks: RCTDirectEventBlock?
@objc var onTextTrackDataChanged: RCTDirectEventBlock?

@objc
func _onPictureInPictureStatusChanged() {
Expand Down Expand Up @@ -1386,4 +1387,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
self.onAudioTracks?(["audioTracks": audioTracks])
}
}

func handleLegibleOutput(strings: [NSAttributedString]) {
if let subtitles = strings.first {
self.onTextTrackDataChanged?(["subtitleTracks": subtitles.string])
}
}
}
1 change: 1 addition & 0 deletions ios/Video/RCTVideoManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onTextTrackDataChanged, RCTDirectEventBlock);

RCT_EXTERN_METHOD(save
: (NSDictionary*)options reactTag
Expand Down
10 changes: 10 additions & 0 deletions src/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
OnProgressData,
OnReceiveAdEventData,
OnSeekData,
OnTextTrackDataChangedData,
OnTextTracksData,
OnTimedMetadataData,
OnVideoAspectRatioData,
Expand Down Expand Up @@ -93,6 +94,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
onTimedMetadata,
onAudioTracks,
onTextTracks,
onTextTrackDataChanged,
onVideoTracks,
onAspectRatio,
...rest
Expand Down Expand Up @@ -333,6 +335,13 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[onTextTracks],
);

const _onTextTrackDataChanged = useCallback(
(e: NativeSyntheticEvent<OnTextTrackDataChangedData>) => {
onTextTrackDataChanged?.(e.nativeEvent);
},
[onTextTrackDataChanged],
);

KrzysztofMoch marked this conversation as resolved.
Show resolved Hide resolved
const _onVideoTracks = useCallback(
(e: NativeSyntheticEvent<OnVideoTracksData>) => {
onVideoTracks?.(e.nativeEvent);
Expand Down Expand Up @@ -509,6 +518,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
onTimedMetadata={_onTimedMetadata}
onAudioTracks={_onAudioTracks}
onTextTracks={_onTextTracks}
onTextTrackDataChanged={_onTextTrackDataChanged}
onVideoTracks={_onVideoTracks}
onVideoFullscreenPlayerDidDismiss={onFullscreenPlayerDidDismiss}
onVideoFullscreenPlayerDidPresent={onFullscreenPlayerDidPresent}
Expand Down
10 changes: 9 additions & 1 deletion src/VideoNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import {NativeModules, requireNativeComponent} from 'react-native';
import type ResizeMode from './types/ResizeMode';
import type FilterType from './types/FilterType';
import type Orientation from './types/Orientation';
import type {AdEvent, EnumValues, OnTextTracksTypeData} from './types';
import type {
AdEvent,
EnumValues,
OnTextTrackDataChangedData,
OnTextTracksTypeData,
} from './types';

// -------- There are types for native component (future codegen) --------
// if you are looking for types for react component, see src/types/video.ts
Expand Down Expand Up @@ -366,6 +371,9 @@ export interface VideoNativeProps extends ViewProps {
onTimedMetadata?: (event: NativeSyntheticEvent<OnTimedMetadataData>) => void; // ios, android
onAudioTracks?: (event: NativeSyntheticEvent<OnAudioTracksData>) => void; // android
onTextTracks?: (event: NativeSyntheticEvent<OnTextTracksData>) => void; // android
onTextTrackDataChanged?: (
event: NativeSyntheticEvent<OnTextTrackDataChangedData>,
) => void; // iOS
onVideoTracks?: (event: NativeSyntheticEvent<OnVideoTracksData>) => void; // android
}

Expand Down
5 changes: 5 additions & 0 deletions src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export type OnTextTracksData = Readonly<{
textTracks: ReadonlyArray<TextTrack>;
}>;

export type OnTextTrackDataChangedData = Readonly<{
subtitleTracks: string;
}>;

export type OnVideoTracksData = Readonly<{
videoTracks: ReadonlyArray<
Readonly<{
Expand Down Expand Up @@ -181,6 +185,7 @@ export interface ReactVideoEvents {
onTimedMetadata?: (e: OnTimedMetadataData) => void; //Android, iOS
onAudioTracks?: (e: OnAudioTracksData) => void; // Android
onTextTracks?: (e: OnTextTracksData) => void; //Android
onTextTrackDataChanged?: (e: OnTextTrackDataChangedData) => void; // iOS
onVideoTracks?: (e: OnVideoTracksData) => void; //Android
onAspectRatio?: (e: OnVideoAspectRatioData) => void;
}
Loading