Skip to content

Commit

Permalink
Hls player go live action (#1386)
Browse files Browse the repository at this point in the history
Closes #JIRA_270, #JIRA_274

### Pre-launch Checklist

- [x] The [Documentation] is updated accordingly, or this PR doesn't
require it.
- [x] I have updated the `ExampleAppChangelog.txt` file with relevant
changes.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
test-exempt.
- [x] All existing and new tests are passing.

<!-- Links -->

[Documentation]: https://www.100ms.live/docs
  • Loading branch information
stanwolverine committed Apr 26, 2024
2 parents cf88633 + c27360e commit b9bfd4b
Show file tree
Hide file tree
Showing 48 changed files with 1,596 additions and 290 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.reactnativehmssdk
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
Expand All @@ -28,7 +29,7 @@ class HMSHLSPlayer(context: ReactContext) : FrameLayout(context) {
private var hmsHlsPlayer: HmsHlsPlayer? = null // 100ms HLS Player
private var hmssdkInstance: HMSSDK? = null
private var statsMonitorAttached = false
private var shouldSendCaptionsToJS = true
private var shouldSendCaptionsToJS = false
private val hmsHlsPlaybackEventsObject =
object : HmsHlsPlaybackEvents {
override fun onCue(cue: HmsHlsCue) {
Expand Down Expand Up @@ -119,6 +120,7 @@ class HMSHLSPlayer(context: ReactContext) : FrameLayout(context) {
val localPlayerView = view.findViewById<PlayerView>(R.id.hls_view)
playerView = localPlayerView
localPlayerView.useController = false
localPlayerView.subtitleView?.visibility = View.GONE

val hmssdkCollection = context.getNativeModule(HMSManager::class.java)?.getHmsInstance()
hmssdkInstance = hmssdkCollection?.get("12345")?.hmsSDK
Expand Down Expand Up @@ -238,6 +240,15 @@ class HMSHLSPlayer(context: ReactContext) : FrameLayout(context) {
sendHLSPlayerCuesEventToJS(null)
}

fun getPlayerDurationDetails(requestId: Int) {
val data: WritableMap = Arguments.createMap()
hmsHlsPlayer?.getNativePlayer()?.let { exoPlayer ->
data.putInt("rollingWindowTime", exoPlayer.seekParameters.toleranceAfterUs.div(1000).toInt())
data.putInt("streamDuration", exoPlayer.duration.toInt())
}
sendHLSDataRequestEventToJS(requestId, data)
}

fun enableStats(enable: Boolean) {
if (enable) {
attachStatsMonitor()
Expand All @@ -249,11 +260,13 @@ class HMSHLSPlayer(context: ReactContext) : FrameLayout(context) {
fun enableControls(show: Boolean) {
playerView?.let {
if (show) {
it.subtitleView?.visibility = View.VISIBLE
it.useController = true
it.showController()
} else {
it.hideController()
it.useController = false
it.subtitleView?.visibility = View.GONE
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ class HMSHLSPlayerManager : SimpleViewManager<HMSHLSPlayer>() {
}
110 -> root.enableClosedCaption()
120 -> root.disableClosedCaption()
130 -> {
args.let {
if (it != null) {
root.getPlayerDurationDetails(it.getInt(0))
}
}
}
}
}

Expand All @@ -105,6 +112,7 @@ class HMSHLSPlayerManager : SimpleViewManager<HMSHLSPlayer>() {
.put("isClosedCaptionEnabled", 100)
.put("enableClosedCaption", 110)
.put("disableClosedCaption", 120)
.put("getPlayerDurationDetails", 130)
.build()
}

Expand Down
1 change: 1 addition & 0 deletions packages/react-native-hms/ios/HMSHLSPlayerManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ @interface RCT_EXTERN_MODULE(HMSHLSPlayerManager, RCTViewManager)
RCT_EXTERN_METHOD(isClosedCaptionEnabled:(nonnull NSNumber *)node requestId:(nonnull NSNumber *)requestId)
RCT_EXTERN_METHOD(enableClosedCaption:(nonnull NSNumber *)node)
RCT_EXTERN_METHOD(disableClosedCaption:(nonnull NSNumber *)node)
RCT_EXTERN_METHOD(getPlayerDurationDetails:(nonnull NSNumber *)node requestId:(nonnull NSNumber *)requestId)

@end
24 changes: 24 additions & 0 deletions packages/react-native-hms/ios/HMSHLSPlayerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ class HMSHLSPlayerManager: RCTViewManager {
}
}
}

@objc func getPlayerDurationDetails(_ node: NSNumber, requestId: NSNumber) {
DispatchQueue.main.async {
if let component = self.bridge.uiManager.view(forReactTag: node) as? HMSHLSPlayer {
component.getPlayerDurationDetails(requestId: UInt(truncating: requestId))
}
}
}
}

class HMSHLSPlayer: UIView {
Expand Down Expand Up @@ -267,6 +275,22 @@ class HMSHLSPlayer: UIView {
playerItem.select(nil, in: subtitle)
}

@objc func getPlayerDurationDetails(requestId: UInt) {
var map = [String: Any?]()
guard let playerItem = hmsHLSPlayer._nativePlayer.currentItem else {
sendRequestedDataToJS(requestId, map)
return
}
// If duration is known
if !playerItem.duration.isIndefinite {
map["streamDuration"] = playerItem.duration.seconds
}
if let timeRange = playerItem.seekableTimeRanges.last as? CMTimeRange {
map["rollingWindowTime"] = timeRange.duration.seconds * 1000
}
sendRequestedDataToJS(requestId, map)
}

@objc func pause() {
hmsHLSPlayer.pause()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useImperativeHandle, useMemo, useRef } from 'react';
import React, { useEffect, useImperativeHandle, useMemo, useRef } from 'react';
import {
View,
StyleSheet,
Expand Down Expand Up @@ -32,9 +32,14 @@ import {
HMSHLSPlayerPlaybackEventTypes,
HMSHLSPlayerStatsEventTypes,
} from '../../types';
import type { HMSHLSPlayerPlaybackCueEventData } from '../../types';
import type {
HLSPlayerDurationDetails,
HMSHLSPlayerPlaybackCueEventData,
} from '../../types';
import { HMSEncoder } from '../../classes/HMSEncoder';
import type { HMSHLSPlayerPlaybackCue } from '../../stores/types';
import { useHMSStore } from '../../stores/hms-store';
import { useHMSHLSPlayerStatsStore } from '../../stores/hls-player-stats-store';

export interface HMSHLSPlayerProps {
url?: string;
Expand All @@ -58,6 +63,7 @@ export interface HMSHLSPlayerRefProperties {
isClosedCaptionEnabled: () => Promise<boolean>;
enableClosedCaption: () => void;
disableClosedCaption: () => void;
getPlayerDurationDetails: () => Promise<HLSPlayerDurationDetails>;
}

const _HMSHLSPlayer: React.ForwardRefRenderFunction<
Expand Down Expand Up @@ -261,6 +267,30 @@ const _HMSHLSPlayer: React.ForwardRefRenderFunction<
);
}
},
getPlayerDurationDetails: () => {
if (
hmsHlsPlayerRef.current &&
RCTHMSHLSPlayerViewManagerConfig.Commands.getPlayerDurationDetails
) {
const requestId = currentRequestId.current++;
const promise = new Promise<HLSPlayerDurationDetails>(
(resolve, reject) => {
promiseAndIdsMap.set(requestId, { resolve, reject });
}
);

UIManager.dispatchViewManagerCommand(
findNodeHandle(hmsHlsPlayerRef.current),
RCTHMSHLSPlayerViewManagerConfig.Commands.getPlayerDurationDetails,
[requestId]
);
return promise;
}
return Promise.resolve({
streamDuration: undefined,
rollingWindowTime: undefined,
});
},
}),
[currentRequestId, promiseAndIdsMap]
);
Expand Down Expand Up @@ -331,6 +361,13 @@ const _HMSHLSPlayer: React.ForwardRefRenderFunction<
promiseMethods.resolve(data);
};

useEffect(() => {
return () => {
useHMSStore.getState().resetPlaybackSlice();
useHMSHLSPlayerStatsStore.getState().reset();
};
}, []);

return (
<View style={[styles.container, containerStyle]}>
<View style={[styles.playerWrapper, style]}>
Expand Down
13 changes: 9 additions & 4 deletions packages/react-native-hms/src/components/HMSHLSPlayer/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect } from 'react';
import { Platform } from 'react-native';
import type { DependencyList } from 'react';
import { shallow } from 'zustand/shallow';

Expand Down Expand Up @@ -43,10 +44,14 @@ export const useHMSHLSPlayerSubtitles = () => {
return useHMSHLSPlayerStatsStore((state) => state.subtitles);
};

// // The distance of current playback position from the live edge of HLS stream
// export const useIsHLSStreamLive = (liveOffsetMillis: number = 1000) => {
// return useHMSHLSPlayerStatsStore((state) => state.stats.distanceFromLive < liveOffsetMillis);
// }
// The distance of current playback position from the live edge of HLS stream
export const useIsHLSStreamLive = (
liveOffsetMillis: number = Platform.select({ default: 10000, ios: 5000 })
) => {
return useHMSHLSPlayerStatsStore(
(state) => state.stats.distanceFromLive < liveOffsetMillis
);
};

// get latest state (without component rerender)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export {
useHMSHLSPlayerStatsError,
useHMSHLSPlayerResolution,
useHMSHLSPlayerSubtitles,
useIsHLSStreamLive,
} from './hooks';
1 change: 1 addition & 0 deletions packages/react-native-hms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export * from './classes/HMSCameraControl';
export * from './classes/HMSIOSAudioMode';
export * from './classes/HMSRecordingState';
export * from './classes/HMSStreamingState';
export * from './classes/HMSHLSPlaylistType';
export type {
HMSSessionStore,
JsonArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ export const createHMSHLSPlayerPlaybackSlice: StateCreator<

resolution: undefined,
setResolution: (resolution) => set({ resolution }),

resetPlaybackSlice: () =>
set({
cue: undefined,
playbackState: HMSHLSPlayerPlaybackState.UNKNOWN,
error: undefined,
resolution: undefined,
}),
});
42 changes: 24 additions & 18 deletions packages/react-native-hms/src/stores/hls-player-stats-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,30 @@ import type {
HMSHLSPlayerStatsStore,
} from './types';

export const useHMSHLSPlayerStatsStore = create<HMSHLSPlayerStatsStore>()(
subscribeWithSelector((set) => ({
// Handle Stats
stats: {
// bandwidth
bandWidthEstimate: 0,
totalBytesLoaded: 0,
const INITIAL_STATS = {
// bandwidth
bandWidthEstimate: 0,
totalBytesLoaded: 0,

// bufferedDuration
bufferedDuration: 0,

// bufferedDuration
bufferedDuration: 0,
// distanceFromLive
distanceFromLive: 0,

// distanceFromLive
distanceFromLive: 0,
// frameInfo
droppedFrameCount: 0,

// frameInfo
droppedFrameCount: 0,
// videoInfo
averageBitrate: 0,
videoHeight: 0,
videoWidth: 0,
};

// videoInfo
averageBitrate: 0,
videoHeight: 0,
videoWidth: 0,
},
export const useHMSHLSPlayerStatsStore = create<HMSHLSPlayerStatsStore>()(
subscribeWithSelector((set) => ({
// Handle Stats
stats: INITIAL_STATS,
changeStats: (stats: HMSHLSPlayerStats) => set({ stats }),

// Handle Stats Error
Expand All @@ -37,5 +39,9 @@ export const useHMSHLSPlayerStatsStore = create<HMSHLSPlayerStatsStore>()(
// Handle Closed Caption
subtitles: null,
setSubtitles: (subtitles: string | null) => set({ subtitles }),

// Reset State
reset: () =>
set({ stats: INITIAL_STATS, error: undefined, subtitles: null }),
}))
);
2 changes: 2 additions & 0 deletions packages/react-native-hms/src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface HMSHLSPlayerPlaybackSlice {
setPlaybackState(playbackState: HMSHLSPlayerPlaybackState): void;
setResolution(resolution: HMSHLSPlayerResolution): void;
setPlaybackError(error: HMSHLSPlayerPlaybackError): void;
resetPlaybackSlice(): void;
}

//#endregion HLS Player Playback Slice
Expand All @@ -74,6 +75,7 @@ export interface HMSHLSPlayerStatsSlice {
error: HMSHLSPlayerStatsError | undefined;
changeStats(stats: HMSHLSPlayerStats): void;
setError(error: HMSHLSPlayerStatsError): void;
reset(): void;
}

export interface HLSPlayerClosedCaptionsSlice {
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native-hms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ export type RequestedDataEvent = {
data: any;
};

export type HLSPlayerDurationDetails = {
streamDuration?: number;
rollingWindowTime?: number;
};

// #endregion HMS HLSPlayer Stats Events

// #region Utility types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ android {
applicationId "live.hms.rn"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 467
versionName "2.4.17"
versionCode 470
versionName "2.4.20"
missingDimensionStrategy 'react-native-camera', 'general'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@
CODE_SIGN_ENTITLEMENTS = RNExample/RNExample.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 467;
CURRENT_PROJECT_VERSION = 470;
DEVELOPMENT_TEAM = 5N85PP82A9;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = RNExample/Info.plist;
Expand Down Expand Up @@ -540,7 +540,7 @@
CODE_SIGN_ENTITLEMENTS = RNExample/RNExample.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 467;
CURRENT_PROJECT_VERSION = 470;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5N85PP82A9;
INFOPLIST_FILE = RNExample/Info.plist;
Expand Down Expand Up @@ -703,7 +703,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = RNExampleBroadcastUpload/RNExampleBroadcastUpload.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 467;
CURRENT_PROJECT_VERSION = 470;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 5N85PP82A9;
GCC_C_LANGUAGE_STANDARD = gnu11;
Expand Down Expand Up @@ -745,7 +745,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 467;
CURRENT_PROJECT_VERSION = 470;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5N85PP82A9;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.4.17</string>
<string>2.4.20</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>467</string>
<string>470</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b9bfd4b

Please sign in to comment.