From b171ebdf80113321fbb1f8b4ef06c90233766465 Mon Sep 17 00:00:00 2001 From: Juozas Petkelis Date: Thu, 4 Dec 2025 14:17:20 +0200 Subject: [PATCH 1/2] feat: add new apis --- README.md | 2 +- .../ivs/reactnative/player/AmazonIvsView.kt | 62 +++++++++++++- .../player/AmazonIvsViewManager.kt | 14 ++++ docs/types.md | 6 ++ ios/AmazonIvs.mm | 84 ++++++++++++------- ios/AmazonIvsView.swift | 62 +++++++++++++- .../AmazonIvsViewNativeComponent.ts | 6 ++ src/components/IVSPlayer.tsx | 47 ++++++++++- src/types/index.ts | 5 +- 9 files changed, 248 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 6e8da8a9..045b708d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This package implements native binding for Amazon IVS Player for iOS and Android Mobile Only. | Android Mobile | iOS | Android TV | tvOS | | :-----: | :-: | :---: | :-: | -| SDK >= 21 | >= 13 | **X** | **X** | +| SDK >= 24 | >= 15.1 | **X** | **X** | ## Installation diff --git a/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt b/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt index 2483c46a..3e4554fc 100644 --- a/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt +++ b/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt @@ -80,11 +80,13 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte LOAD_START("onLoadStart"), REBUFFERING("onRebuffering"), SEEK("onSeek"), + SEEK_COMPLETE("onSeekComplete"), DATA("onData"), LIVE_LATENCY_CHANGED("onLiveLatencyChange"), VIDEO_STATISTICS("onVideoStatistics"), PROGRESS("onProgress"), - TIME_POINT("onTimePoint"); + TIME_POINT("onTimePoint"), + VIDEO_SIZE_CHANGE("onVideoSizeChange"); override fun toString(): String { return mName @@ -125,6 +127,7 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte override fun onSeekCompleted(position: Long) { onSeek(position) + onSeekComplete(true) } override fun onQualityChanged(quality: Quality) { @@ -132,6 +135,8 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte } override fun onVideoSizeChanged(p0: Int, p1: Int) { + onVideoSizeChange(p0, p1) + post(mLayoutRunnable) } @@ -610,6 +615,40 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte ) } + fun onVideoSizeChange(p0: Int, p1: Int) { + val reactContext = context as ReactContext + val size = Arguments.createMap() + size.putInt("height", p0) + size.putInt("width", p1) + + val data = Arguments.createMap() + data.putMap("size", size) + + eventDispatcher.dispatchEvent( + IVSEvent( + getSurfaceId(reactContext), + id, + Events.VIDEO_SIZE_CHANGE, + data + ) + ) + } + + fun onSeekComplete(success: Boolean) { + val reactContext = context as ReactContext + val data = Arguments.createMap() + data.putBoolean("success", success) + + eventDispatcher.dispatchEvent( + IVSEvent( + getSurfaceId(reactContext), + id, + Events.SEEK_COMPLETE, + data + ) + ) + } + fun onPlayerRebuffering() { val reactContext = context as ReactContext @@ -784,6 +823,19 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte }, 0, updatedProgressInterval) } + fun setMaxVideoSize(maxSize: ReadableMap?) { + val width = maxSize?.getInt("width") + val height = maxSize?.getInt("height") + + if (width != null && height != null) { + player?.setAutoMaxVideoSize(width, height) + } + } + + fun setNetworkRecoveryMode(mode: String?) { + player?.setNetworkRecoveryMode(findNetworkRecoveryMode((mode))) + } + override fun onHostResume() { isInBackground = false stopBackgroundService() @@ -867,4 +919,12 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte } } + private fun findNetworkRecoveryMode(mode: String?): Player.NetworkRecoveryMode { + return when (mode) { + "none" -> Player.NetworkRecoveryMode.NONE + "resume" -> Player.NetworkRecoveryMode.RESUME + else -> Player.NetworkRecoveryMode.NONE + } + } + } diff --git a/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsViewManager.kt b/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsViewManager.kt index cab39143..cbea7f1f 100644 --- a/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsViewManager.kt +++ b/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsViewManager.kt @@ -189,6 +189,20 @@ class AmazonIvsViewManager : SimpleViewManager(), view?.setProgressInterval(value) } + override fun setMaxVideoSize( + view: AmazonIvsView?, + value: ReadableMap? + ) { + view?.setMaxVideoSize(value) + } + + override fun setNetworkRecoveryMode( + view: AmazonIvsView?, + value: String? + ) { + view?.setNetworkRecoveryMode(value) + } + override fun setPlayInBackground( view: AmazonIvsView?, value: Boolean diff --git a/docs/types.md b/docs/types.md index 4aa058d1..fa10ca7c 100644 --- a/docs/types.md +++ b/docs/types.md @@ -75,6 +75,12 @@ export type TextMetadataCue = { export type ResizeMode = 'aspectFill' | 'aspectFit' | 'aspectZoom'; ``` +## NetworkRecoveryMode + +```ts +export type NetworkRecoveryMode = 'none' | 'resume'; +``` + ## Source ```ts diff --git a/ios/AmazonIvs.mm b/ios/AmazonIvs.mm index f8ab2419..4553311f 100644 --- a/ios/AmazonIvs.mm +++ b/ios/AmazonIvs.mm @@ -29,8 +29,7 @@ + (ComponentDescriptorProvider)componentDescriptorProvider { } // disable view recycling otherwise AV resourses fail to load on recycled view -+ (BOOL)shouldBeRecycled -{ ++ (BOOL)shouldBeRecycled { return NO; } @@ -112,6 +111,14 @@ - (instancetype)initWithFrame:(CGRect)frame { _ivsView.onTimePoint = ^(NSDictionary *onTimePointPayload) { [weakSelf onTimePoint:onTimePointPayload]; }; + + _ivsView.onVideoSizeChange = ^(NSDictionary *onVideoSizeChangePayload) { + [weakSelf onVideoSizeChange:onVideoSizeChangePayload]; + }; + + _ivsView.onSeekComplete = ^(NSDictionary *onSeekCompletePayload) { + [weakSelf onSeekComplete:onSeekCompletePayload]; + }; } self.contentView = _ivsView; @@ -207,6 +214,20 @@ - (void)updateProps:(Props::Shared const &)props _ivsView.playInBackground = newViewProps.playInBackground; } + if (oldViewProps.networkRecoveryMode != newViewProps.networkRecoveryMode) { + _ivsView.networkRecoveryMode = @(newViewProps.networkRecoveryMode.c_str()); + } + + if (oldViewProps.maxVideoSize.size.width != + newViewProps.maxVideoSize.size.width || + oldViewProps.maxVideoSize.size.height != + newViewProps.maxVideoSize.size.height) { + _ivsView.maxVideoSize = @{ + @"width" : @(newViewProps.maxVideoSize.size.width), + @"height" : @(newViewProps.maxVideoSize.size.height) + }; + } + [super updateProps:props oldProps:oldProps]; } @@ -457,7 +478,6 @@ - (void)onTimePoint:(NSDictionary *)onTimePointPayload { const auto eventEmitter = [self getEventEmitter]; double position = [onTimePointPayload[@"position"] doubleValue]; - ; AmazonIvsEventEmitter::OnTimePoint eventData; eventData.position = position; @@ -536,45 +556,51 @@ - (void)onLiveLatencyChange:(NSDictionary *)onLiveLatencyChangePayload { - (void)onQualityChange:(NSDictionary *)onQualityChangePayload { const auto eventEmitter = [self getEventEmitter]; - id qualityObj = onQualityChangePayload[@"quality"]; - - if (qualityObj == nil || qualityObj == [NSNull null]) { - return; - } - AmazonIvsEventEmitter::OnQualityChange eventData; - NSDictionary *qualityData = (NSDictionary *)qualityObj; - AmazonIvsEventEmitter::OnQualityChangeQuality qualityStruct; + NSDictionary *qualityData = onQualityChangePayload[@"quality"]; - id nameObj = qualityData[@"name"]; - qualityStruct.name = (nameObj != [NSNull null]) ? [nameObj UTF8String] : ""; + std::string name = [qualityData[@"name"] UTF8String] ?: ""; + std::string codecs = [qualityData[@"codecs"] UTF8String] ?: ""; + int bitrate = [qualityData[@"bitrate"] intValue]; + double framerate = [qualityData[@"framerate"] doubleValue]; + int width = [qualityData[@"width"] intValue]; + int height = [qualityData[@"height"] intValue]; - id codecsObj = qualityData[@"codecs"]; - qualityStruct.codecs = - (codecsObj != [NSNull null]) ? [codecsObj UTF8String] : ""; + eventData.quality = {name, codecs, bitrate, framerate, width, height}; - id bitrateObj = qualityData[@"bitrate"]; - qualityStruct.bitrate = - (bitrateObj != [NSNull null]) ? [bitrateObj intValue] : 0; + if (eventEmitter != nullptr) { + eventEmitter->onQualityChange(eventData); + } +} - id framerateObj = qualityData[@"framerate"]; - qualityStruct.framerate = - (framerateObj != [NSNull null]) ? [framerateObj doubleValue] : 0.0; +- (void)onSeekComplete:(NSDictionary *)onSeekCompletePayload { + const auto eventEmitter = [self getEventEmitter]; + AmazonIvsEventEmitter::OnSeekComplete eventData; + + BOOL success = [onSeekCompletePayload[@"success"] boolValue]; + eventData.success = success; + + if (eventEmitter != nullptr) { + eventEmitter->onSeekComplete(eventData); + } +} - id widthObj = qualityData[@"width"]; - qualityStruct.width = (widthObj != [NSNull null]) ? [widthObj intValue] : 0; +- (void)onVideoSizeChange:(NSDictionary *)onVideoSizeChangePayload { + const auto eventEmitter = [self getEventEmitter]; + AmazonIvsEventEmitter::OnVideoSizeChange eventData; - id heightObj = qualityData[@"height"]; - qualityStruct.height = - (heightObj != [NSNull null]) ? [heightObj intValue] : 0; + NSDictionary *videoSize = onVideoSizeChangePayload[@"size"]; - eventData.quality = qualityStruct; + int width = [videoSize[@"width"] intValue]; + int height = [videoSize[@"height"] intValue]; + eventData.size = {width, height}; if (eventEmitter != nullptr) { - eventEmitter->onQualityChange(eventData); + eventEmitter->onVideoSizeChange(eventData); } } + - (void)onLoad:(NSDictionary *)durationDict { const auto eventEmitter = [self getEventEmitter]; diff --git a/ios/AmazonIvsView.swift b/ios/AmazonIvsView.swift index d4eae042..82119dea 100644 --- a/ios/AmazonIvsView.swift +++ b/ios/AmazonIvsView.swift @@ -5,6 +5,7 @@ import UIKit @objcMembers public class AmazonIvsView: UIView, IVSPlayer.Delegate { public var onSeek: ((NSDictionary) -> Void)? + public var onSeekComplete: ((NSDictionary) -> Void)? public var onData: ((NSDictionary) -> Void)? public var onVideoStatistics: ((NSDictionary) -> Void)? public var onPlayerStateChange: ((NSDictionary) -> Void)? @@ -20,6 +21,7 @@ import UIKit public var onProgress: ((NSDictionary) -> Void)? public var onTimePoint: ((NSDictionary?) -> Void)? public var onError: ((NSDictionary) -> Void)? + public var onVideoSizeChange: ((NSDictionary) -> Void)? private let player = IVSPlayer() private let playerView = IVSPlayerView() @@ -168,7 +170,9 @@ import UIKit public var quality: NSDictionary? { didSet { - let newQuality = findQuality(quality: quality) + guard let newQuality = findQuality(quality: quality) else { + return + } player.quality = newQuality } } @@ -294,6 +298,14 @@ import UIKit } } + public var networkRecoveryMode: String? { + didSet { + player.setNetworkRecoveryMode( + findNetworkRecoveryMode(mode: networkRecoveryMode) + ) + } + } + private func findResizeMode(mode: String?) -> AVLayerVideoGravity { switch mode { case "aspectFill": @@ -307,6 +319,15 @@ import UIKit } } + public var maxVideoSize: NSDictionary? { + didSet { + guard let size = findSize(size: maxVideoSize) else { + return + } + player.setMaxVideoSize(size) + } + } + public var breakpoints: NSArray { didSet { self.removeTimePointObserver() @@ -332,7 +353,9 @@ import UIKit position, preferredTimescale: 1_000_000 ) - player.seek(to: parsedTime) + player.seek(to: parsedTime) { [weak self] finished in + self?.onSeekComplete?(["success": finished]) + } } public func setOrigin(origin: NSString) { @@ -348,7 +371,7 @@ import UIKit } } - @objc public func loadSource(id: Int) { + public func loadSource(id: Int) { if let source = preloadSourceMap[id] { player.load(source.uri) } @@ -425,6 +448,29 @@ import UIKit } } + private func findNetworkRecoveryMode(mode: String?) + -> IVSPlayer.NetworkRecoveryMode + { + switch mode { + case "none": + return IVSPlayer.NetworkRecoveryMode.none + case "resume": + return IVSPlayer.NetworkRecoveryMode.resume + default: + return IVSPlayer.NetworkRecoveryMode.none + } + } + + private func findSize(size: NSDictionary?) -> CGSize? { + guard let width = (size?["width"] as? NSNumber)?.doubleValue, + let height = (size?["height"] as? NSNumber)?.doubleValue + else { + return nil + } + + return CGSize(width: width, height: height) + } + func addProgressObserver() { progressObserverToken = player.addPeriodicTimeObserver( forInterval: CMTime( @@ -570,13 +616,21 @@ import UIKit "type": cue.type.rawValue, "text": cue.text, "textDescription": cue.textDescription, - ] onTextMetadataCue?(["textMetadataCue": textMetadataCue]) } } + public func player(_ player: IVSPlayer, didChangeVideoSize videoSize: CGSize) + { + let size = [ + "width": Int(videoSize.width), + "height": Int(videoSize.height), + ] + onVideoSizeChange?(["size": size]) + } + public func playerWillRebuffer(_ player: IVSPlayer) { onRebuffering?() } diff --git a/src/components/AmazonIvsViewNativeComponent.ts b/src/components/AmazonIvsViewNativeComponent.ts index d07e550e..6a034551 100644 --- a/src/components/AmazonIvsViewNativeComponent.ts +++ b/src/components/AmazonIvsViewNativeComponent.ts @@ -37,6 +37,8 @@ export interface NativeProps extends ViewProps { initialBufferDuration?: Double; pipEnabled?: boolean; progressInterval?: Double; + maxVideoSize?: { size: { width: Int32; height: Int32 } }; + networkRecoveryMode?: string; playInBackground?: boolean; notificationTitle?: string; notificationText?: string; @@ -102,6 +104,10 @@ export interface NativeProps extends ViewProps { onLoad?: DirectEventHandler<{ duration?: Double }>; onRebuffering?: DirectEventHandler<{}>; onTimePoint?: DirectEventHandler<{ position?: Double }>; + onVideoSizeChange?: DirectEventHandler<{ + size: { width: Int32; height: Int32 }; + }>; + onSeekComplete?: DirectEventHandler<{ success: boolean }>; } interface NativeCommands { diff --git a/src/components/IVSPlayer.tsx b/src/components/IVSPlayer.tsx index 8e2694d6..2188d351 100644 --- a/src/components/IVSPlayer.tsx +++ b/src/components/IVSPlayer.tsx @@ -14,6 +14,7 @@ import { } from 'react-native'; import type { IVSPlayerRef, + NetworkRecoveryMode, PlayerData, Quality, ResizeMode, @@ -53,6 +54,8 @@ export type Props = { maxBitrate?: number; initialBufferDuration?: number; pipEnabled?: boolean; + networkRecoveryMode?: NetworkRecoveryMode; + maxVideoSize?: { size: { width: number; height: number } }; showErrorMessage?: boolean; playInBackground?: boolean; notificationTitle?: string; @@ -73,6 +76,7 @@ export type Props = { onProgress?(progress: number): void; onError?(error: string): void; onTimePoint?(position: number): void; + onVideoSizeChange?(size: { width: number; height: number }): void; children?: React.ReactNode; }; @@ -106,6 +110,8 @@ const IVSPlayerContainer = React.forwardRef( breakpoints = [], maxBitrate, initialBufferDuration, + networkRecoveryMode, + maxVideoSize, showErrorMessage, playInBackground = false, notificationTitle, @@ -126,12 +132,15 @@ const IVSPlayerContainer = React.forwardRef( onProgress, onError, onTimePoint, + onVideoSizeChange, children, }, ref ) => { const mediaPlayerRef = useRef(null); const initialized = useRef(false); + const liveLatencyRef = useRef(null); + const seekCallbackRef = useRef<((success: boolean) => void) | null>(null); const [errorMessage, setErrorMessage] = useState(); const preload = (url: string) => { @@ -167,8 +176,11 @@ const IVSPlayerContainer = React.forwardRef( Commands.pause(mediaPlayerRef.current); }; - const seekTo = (value: number) => { + const seekTo = (value: number, callback?: (success: boolean) => void) => { if (!mediaPlayerRef.current) return; + if (callback) { + seekCallbackRef.current = callback; + } Commands.seekTo(mediaPlayerRef.current, value); }; @@ -182,6 +194,10 @@ const IVSPlayerContainer = React.forwardRef( Commands.togglePip(mediaPlayerRef.current); }; + const getLiveLatency = () => { + return liveLatencyRef.current; + }; + useEffect(() => { if (initialized.current || autoplay) { if (paused) { @@ -204,6 +220,7 @@ const IVSPlayerContainer = React.forwardRef( seekTo, setOrigin, togglePip, + getLiveLatency, }), [ preload, @@ -214,6 +231,7 @@ const IVSPlayerContainer = React.forwardRef( seekTo, setOrigin, togglePip, + getLiveLatency, ] ); @@ -272,6 +290,7 @@ const IVSPlayerContainer = React.forwardRef( event: NativeSyntheticEvent<{ liveLatency: number }> ) => { const { liveLatency } = event.nativeEvent; + liveLatencyRef.current = liveLatency; onLiveLatencyChange?.(liveLatency); }; @@ -333,6 +352,24 @@ const IVSPlayerContainer = React.forwardRef( onTimePoint?.(position); }; + const onVideoSizeChangeHandler = ( + event: NativeSyntheticEvent<{ size: { width: number; height: number } }> + ) => { + const { size } = event.nativeEvent; + onVideoSizeChange?.(size); + }; + + const onSeekCompleteHandler = ( + event: NativeSyntheticEvent<{ success: boolean }> + ) => { + const { success } = event.nativeEvent; + + if (seekCallbackRef.current) { + seekCallbackRef.current(success); + seekCallbackRef.current = null; + } + }; + const constrainedProgressInterval = useMemo(() => { if (!progressInterval || progressInterval <= 0.1) { return 0.1; @@ -368,6 +405,8 @@ const IVSPlayerContainer = React.forwardRef( playInBackground={playInBackground} notificationTitle={notificationTitle} notificationText={notificationText} + networkRecoveryMode={networkRecoveryMode} + maxVideoSize={maxVideoSize} onVideoStatistics={onVideoStatisticsHandler} onData={onDataHandler} onSeek={onSeekHandler} @@ -381,11 +420,11 @@ const IVSPlayerContainer = React.forwardRef( onTextCue={onTextCueHandler} onTextMetadataCue={onTextMetadataCueHandler} onProgress={onProgressHandler} - onLiveLatencyChange={ - onLiveLatencyChange ? onLiveLatencyChangeHandler : undefined - } + onLiveLatencyChange={onLiveLatencyChangeHandler} onError={onErrorHandler} onTimePoint={onTimePointHandler} + onVideoSizeChange={onVideoSizeChangeHandler} + onSeekComplete={onSeekCompleteHandler} /> {children} diff --git a/src/types/index.ts b/src/types/index.ts index d443edc6..279c85c1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -48,9 +48,12 @@ export type IVSPlayerRef = { releaseSource: (source: Source) => void; play: () => void; pause: () => void; - seekTo: (position: number) => void; + seekTo: (value: number, callback?: (success: boolean) => void) => void; setOrigin: (origin: string) => void; togglePip: () => void; + getLiveLatency: () => number; }; export type ResizeMode = 'aspectFill' | 'aspectFit' | 'aspectZoom'; + +export type NetworkRecoveryMode = 'none' | 'resume'; From 64d0b2d069445e2d8ef1e3df5b91d5f6f0e78a15 Mon Sep 17 00:00:00 2001 From: Juozas Petkelis Date: Thu, 4 Dec 2025 15:40:28 +0200 Subject: [PATCH 2/2] feat: add adaptive param to quality --- .../ivs/reactnative/player/AmazonIvsView.kt | 10 +++-- docs/usage-guide.md | 43 ++++++++++++++++++- example/src/screens/PlaygroundExample.tsx | 4 +- ios/AmazonIvs.mm | 35 +++++++++------ ios/AmazonIvsView.swift | 7 ++- .../AmazonIvsViewNativeComponent.ts | 5 ++- src/components/IVSPlayer.tsx | 5 ++- 7 files changed, 86 insertions(+), 23 deletions(-) diff --git a/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt b/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt index 3e4554fc..ed8b6b25 100644 --- a/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt +++ b/android/src/main/java/com/amazonaws/ivs/reactnative/player/AmazonIvsView.kt @@ -307,9 +307,13 @@ class AmazonIvsView(private val context: ThemedReactContext) : FrameLayout(conte } fun setQuality(quality: ReadableMap?) { - if (quality != null) { - findQuality(quality)?.let { - player?.quality = it + if (quality == null) return + val target = if (quality.hasKey("target")) quality.getMap("target") else null + val isAdaptive = if (quality.hasKey("adaptive")) quality.getBoolean("adaptive") else true + + if (target != null) { + findQuality(target)?.let { selectedQuality-> + player?.setQuality(selectedQuality, isAdaptive) } } } diff --git a/docs/usage-guide.md b/docs/usage-guide.md index 12726e00..bccbc51f 100644 --- a/docs/usage-guide.md +++ b/docs/usage-guide.md @@ -46,6 +46,13 @@ You can also set the video volume or its quality using component props. The whol In order to set video quality, you need to get the list of available qualities that come from the `onData` callback. +Optional `adaptive` flag: + +- `adaptive: false`: Hard-locks the player to the specific resolution. It disables auto-quality (ABR). Use this when a user explicitly selects a resolution (e.g., "1080p"). + +- `adaptive: true`: Switches to the target resolution immediately but keeps auto-quality (ABR) enabled. The player may switch away from this resolution later if network conditions change. + + ```tsx import IVSPlayer, { Quality } from 'amazon-ivs-react-native'; @@ -56,7 +63,31 @@ export default function App() { setQualities(data.qualities)} - quality={qualities[0]} + quality={{ + target: qualities[0], + adaptive: true // Optional: defaults to true. Set to false to force manual mode. + }} + /> + ); +} +``` + +## LIMITING MAXIMUM VIDEO SIZE + +You can limit the maximum video resolution chosen by the auto-quality algorithm using the `maxVideoSize` prop. This is useful for saving bandwidth or restricting quality on specific devices. + +```tsx +import IVSPlayer, { Quality } from 'amazon-ivs-react-native'; + +export default function App() { + const [qualities, setQualities] = useState(); + + return ( + ); } @@ -129,6 +160,10 @@ You can find the full list of events in the [api-reference](./ivs-player-referen onTimePoint={(timePoint) => { console.log('time point', timePoint) }} + onVideoSizeChange={(videoSize) => { + console.log('video width', videoSize.size.width) + console.log('video height', videoSize.size.height) + }} /> ``` @@ -153,7 +188,13 @@ export default function App() { }; const handleSeekToPress = () => { + // Basic usage mediaPlayerRef?.current?.seekTo(15); + + // OR usage with completion callback + mediaPlayerRef?.current?.seekTo(15, (success) => { + console.log('Seek finished, success:', success); + }); }; const handleTogglePipToPress = () => { diff --git a/example/src/screens/PlaygroundExample.tsx b/example/src/screens/PlaygroundExample.tsx index 3281c910..2d898409 100644 --- a/example/src/screens/PlaygroundExample.tsx +++ b/example/src/screens/PlaygroundExample.tsx @@ -174,7 +174,9 @@ export default function PlaygroundExample() { progressInterval={progressInterval} volume={volume} autoQualityMode={autoQualityMode} - quality={manualQuality} + quality={{ + target: manualQuality, + }} autoMaxQuality={autoMaxQuality} breakpoints={breakpoints} onSeek={(newPosition) => console.log('new position', newPosition)} diff --git a/ios/AmazonIvs.mm b/ios/AmazonIvs.mm index 4553311f..dcccd6e3 100644 --- a/ios/AmazonIvs.mm +++ b/ios/AmazonIvs.mm @@ -174,8 +174,12 @@ - (void)updateProps:(Props::Shared const &)props _ivsView.volume = newViewProps.volume; } - if (oldViewProps.quality.name != newViewProps.quality.name) { - _ivsView.quality = DictionaryFromQuality(newViewProps.quality); + if (oldViewProps.quality.target.name != newViewProps.quality.target.name || + oldViewProps.quality.adaptive != newViewProps.quality.adaptive) { + NSDictionary *target = DictionaryFromQuality(newViewProps.quality.target); + + _ivsView.quality = + @{@"adaptive" : @(newViewProps.quality.adaptive), @"target" : target}; } if (oldViewProps.autoMaxQuality.name != newViewProps.autoMaxQuality.name) { @@ -209,7 +213,7 @@ - (void)updateProps:(Props::Shared const &)props if (oldViewProps.progressInterval != newViewProps.progressInterval) { _ivsView.progressInterval = @(newViewProps.progressInterval); } - + if (oldViewProps.playInBackground != newViewProps.playInBackground) { _ivsView.playInBackground = newViewProps.playInBackground; } @@ -558,19 +562,22 @@ - (void)onQualityChange:(NSDictionary *)onQualityChangePayload { const auto eventEmitter = [self getEventEmitter]; AmazonIvsEventEmitter::OnQualityChange eventData; - NSDictionary *qualityData = onQualityChangePayload[@"quality"]; + id qualityObj = onQualityChangePayload[@"quality"]; - std::string name = [qualityData[@"name"] UTF8String] ?: ""; - std::string codecs = [qualityData[@"codecs"] UTF8String] ?: ""; - int bitrate = [qualityData[@"bitrate"] intValue]; - double framerate = [qualityData[@"framerate"] doubleValue]; - int width = [qualityData[@"width"] intValue]; - int height = [qualityData[@"height"] intValue]; + if ([qualityObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *qualityData = (NSDictionary *)qualityObj; - eventData.quality = {name, codecs, bitrate, framerate, width, height}; + std::string name = [qualityData[@"name"] UTF8String] ?: ""; + std::string codecs = [qualityData[@"codecs"] UTF8String] ?: ""; + int bitrate = [qualityData[@"bitrate"] intValue]; + double framerate = [qualityData[@"framerate"] doubleValue]; + int width = [qualityData[@"width"] intValue]; + int height = [qualityData[@"height"] intValue]; - if (eventEmitter != nullptr) { - eventEmitter->onQualityChange(eventData); + eventData.quality = {name, codecs, bitrate, framerate, width, height}; + if (eventEmitter != nullptr) { + eventEmitter->onQualityChange(eventData); + } } } @@ -580,7 +587,7 @@ - (void)onSeekComplete:(NSDictionary *)onSeekCompletePayload { BOOL success = [onSeekCompletePayload[@"success"] boolValue]; eventData.success = success; - + if (eventEmitter != nullptr) { eventEmitter->onSeekComplete(eventData); } diff --git a/ios/AmazonIvsView.swift b/ios/AmazonIvsView.swift index 82119dea..51f94470 100644 --- a/ios/AmazonIvsView.swift +++ b/ios/AmazonIvsView.swift @@ -170,10 +170,13 @@ import UIKit public var quality: NSDictionary? { didSet { - guard let newQuality = findQuality(quality: quality) else { + let isAdaptive = quality?["adaptive"] as? Bool ?? true + let target = quality?["target"] as? NSDictionary + + guard let selectedQuality = findQuality(quality: target) else { return } - player.quality = newQuality + player.setQuality(selectedQuality, adaptive: isAdaptive) } } diff --git a/src/components/AmazonIvsViewNativeComponent.ts b/src/components/AmazonIvsViewNativeComponent.ts index 6a034551..f3da904c 100644 --- a/src/components/AmazonIvsViewNativeComponent.ts +++ b/src/components/AmazonIvsViewNativeComponent.ts @@ -29,7 +29,10 @@ export interface NativeProps extends ViewProps { logLevel?: Int32; resizeMode?: string; volume?: Double; - quality?: Quality | null; + quality?: { + target: Quality | null; + adaptive?: boolean; + }; autoMaxQuality?: Quality | null; autoQualityMode?: boolean; breakpoints?: Int32[]; diff --git a/src/components/IVSPlayer.tsx b/src/components/IVSPlayer.tsx index 2188d351..af19deaf 100644 --- a/src/components/IVSPlayer.tsx +++ b/src/components/IVSPlayer.tsx @@ -47,7 +47,10 @@ export type Props = { resizeMode?: ResizeMode; progressInterval?: number; volume?: number; - quality?: Quality | null; + quality?: { + target: Quality | null; + adaptive?: boolean; + }; autoMaxQuality?: Quality | null; autoQualityMode?: boolean; breakpoints?: number[];