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

Hls player go live action #1386

Merged
merged 15 commits into from
Apr 26, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.