Skip to content

Commit

Permalink
feat: added webrtc timeout caching (#557)
Browse files Browse the repository at this point in the history
* feat: added webrtc timeout caching

* chore: changeset

* fix: player

* fix: controller changes

* fix: remove package manager version
  • Loading branch information
0xcadams authored Jun 20, 2024
1 parent 9e5e3ca commit 2045585
Show file tree
Hide file tree
Showing 13 changed files with 6,882 additions and 5,552 deletions.
8 changes: 8 additions & 0 deletions .changeset/friendly-chairs-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@livepeer/core-react": patch
"@livepeer/core-web": patch
"@livepeer/react": patch
"@livepeer/core": patch
---

**Feature:** added `cacheWebRTCFailureMs` to the player. This allows the player to remember to fall back to HLS if a WebRTC connection times out.
2 changes: 2 additions & 0 deletions examples/next/src/app/player/[type]/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export async function PlayerWithControls({
clipLength={30}
src={src}
jwt={jwt}
cacheWebRTCFailureMs={120000}
timeout={5000}
>
<Player.Container className="h-full w-full overflow-hidden rounded-md bg-gray-950 outline-white/50 outline outline-1 data-[playing=true]:outline-white/80 data-[playing=true]:outline-2 data-[fullscreen=true]:outline-none data-[fullscreen=true]:rounded-none transition-all">
<Player.Video
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@
"*.{js,ts,tsx}": ["pnpm lint:fix"],
"*.{json,md,mdx,yml}": ["pnpm lint:fix"]
},
"packageManager": "[email protected]",
"engines": {
"node": ">=16",
"pnpm": ">=6"
"node": ">=16"
}
}
19 changes: 4 additions & 15 deletions packages/core-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"files": ["dist"],
"exports": {
"./package.json": "./package.json",
".": {
Expand All @@ -31,12 +29,8 @@
},
"typesVersions": {
"*": {
"crypto": [
"./dist/crypto/index.d.ts"
],
"*": [
"./dist/index.d.ts"
]
"crypto": ["./dist/crypto/index.d.ts"],
"*": ["./dist/index.d.ts"]
}
},
"scripts": {
Expand All @@ -61,10 +55,5 @@
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"keywords": [
"livepeer",
"video",
"streaming",
"livestream"
]
"keywords": ["livepeer", "video", "streaming", "livestream"]
}
39 changes: 9 additions & 30 deletions packages/core-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"files": ["dist"],
"exports": {
"./package.json": "./package.json",
".": {
Expand Down Expand Up @@ -56,27 +54,13 @@
},
"typesVersions": {
"*": {
"broadcast": [
"./dist/broadcast/index.d.ts"
],
"browser": [
"./dist/browser/index.d.ts"
],
"external": [
"./dist/external/index.d.ts"
],
"hls": [
"./dist/hls/index.d.ts"
],
"media": [
"./dist/media/index.d.ts"
],
"webrtc": [
"./dist/webrtc/index.d.ts"
],
"*": [
"./dist/index.d.ts"
]
"broadcast": ["./dist/broadcast/index.d.ts"],
"browser": ["./dist/browser/index.d.ts"],
"external": ["./dist/external/index.d.ts"],
"hls": ["./dist/hls/index.d.ts"],
"media": ["./dist/media/index.d.ts"],
"webrtc": ["./dist/webrtc/index.d.ts"],
"*": ["./dist/index.d.ts"]
}
},
"scripts": {
Expand All @@ -90,10 +74,5 @@
"hls.js": "^1.5.8",
"zustand": "^4.5.2"
},
"keywords": [
"livepeer",
"video",
"streaming",
"livestream"
]
"keywords": ["livepeer", "video", "streaming", "livestream"]
}
8 changes: 6 additions & 2 deletions packages/core-web/src/media/controls/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ const addEffectsToStore = (

const onErrorComposed = (err: Error) => {
if (!unmounted) {
cleanupSource?.();

store.getState().__controlsFunctions?.onError?.(err);
}
};
Expand All @@ -426,7 +428,7 @@ const addEffectsToStore = (
const unsubscribeBframes = store.subscribe(
(state) => Boolean(state?.__metadata?.bframes),
(bframes) => {
if (bframes) {
if (bframes && !unmounted) {
onErrorComposed(new Error(BFRAMES_ERROR_MESSAGE));
}
},
Expand All @@ -453,7 +455,9 @@ const addEffectsToStore = (
});

const id = setTimeout(() => {
if (!store.getState().canPlay) {
if (!store.getState().canPlay && !unmounted) {
store.getState().__controlsFunctions.onWebRTCTimeout?.();

onErrorComposed(
new Error(
"Timeout reached for canPlay - triggering playback error.",
Expand Down
21 changes: 4 additions & 17 deletions packages/core-web/src/webrtc/whip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ export const createNewWHIP = <TElement extends HTMLMediaElement>({
export const attachMediaStreamToPeerConnection = async ({
mediaStream,
peerConnection,
}: { mediaStream: MediaStream; peerConnection: RTCPeerConnection }) => {
}: {
mediaStream: MediaStream;
peerConnection: RTCPeerConnection;
}) => {
const newVideoTrack = mediaStream?.getVideoTracks?.()?.[0] ?? null;
const newAudioTrack = mediaStream?.getAudioTracks?.()?.[0] ?? null;

Expand Down Expand Up @@ -259,19 +262,3 @@ export const getDisplayMedia = (options?: DisplayMediaStreamOptions) => {

return navigator.mediaDevices.getDisplayMedia(options);
};

const getConstraints = (aspectRatio: number | null) => {
const constraints: MediaTrackConstraints = {
width: {
ideal: 1280,
},
height: {
ideal: 720,
},
aspectRatio: {
ideal: aspectRatio ?? 16 / 9,
},
};

return constraints;
};
39 changes: 9 additions & 30 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"files": ["dist"],
"exports": {
"./package.json": "./package.json",
".": {
Expand Down Expand Up @@ -56,27 +54,13 @@
},
"typesVersions": {
"*": {
"crypto": [
"./dist/crypto/index.d.ts"
],
"errors": [
"./dist/errors/index.d.ts"
],
"media": [
"./dist/media/index.d.ts"
],
"storage": [
"./dist/storage/index.d.ts"
],
"utils": [
"./dist/utils/index.d.ts"
],
"version": [
"./dist/version/index.d.ts"
],
"*": [
"./dist/index.d.ts"
]
"crypto": ["./dist/crypto/index.d.ts"],
"errors": ["./dist/errors/index.d.ts"],
"media": ["./dist/media/index.d.ts"],
"storage": ["./dist/storage/index.d.ts"],
"utils": ["./dist/utils/index.d.ts"],
"version": ["./dist/version/index.d.ts"],
"*": ["./dist/index.d.ts"]
}
},
"scripts": {
Expand All @@ -92,10 +76,5 @@
"devDependencies": {
"jose": "^5.3.0"
},
"keywords": [
"livepeer",
"video",
"streaming",
"livestream"
]
"keywords": ["livepeer", "video", "streaming", "livestream"]
}
39 changes: 38 additions & 1 deletion packages/core/src/media/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ export type InitialProps = {
*/
clipLength: ClipLength | null;

/**
* How long to cache WebRTC timeouts for faster subsequent playbacks after a timeout.
*
* Set to a number, in ms, to enable caching.
*/
cacheWebRTCFailureMs: number | null;

/**
* Whether hotkeys are enabled. Defaults to `true`. Allows users to use keyboard shortcuts for player control.
*
Expand Down Expand Up @@ -356,6 +363,7 @@ export type MediaControllerState = {
onProgress: (time: number) => void;
onStalled: () => void;
onWaiting: () => void;
onWebRTCTimeout: () => void;
requestClip: () => void;
requestMeasure: () => void;
requestSeek: (time: number) => void;
Expand Down Expand Up @@ -418,6 +426,15 @@ export type MediaControllerStore = StoreApi<MediaControllerState> & {
};
};

let webrtcTimeoutLastTime: number | null = null;

const getHasRecentWebRTCTimeout = (
cacheWebRTCFailureMs: number | null | undefined,
) => {
if (!webrtcTimeoutLastTime || !cacheWebRTCFailureMs) return false;
return Date.now() - webrtcTimeoutLastTime < cacheWebRTCFailureMs;
};

export const createControllerStore = ({
device,
storage,
Expand Down Expand Up @@ -457,6 +474,9 @@ export const createControllerStore = ({
sessionToken,
src,
videoQuality: initialVideoQuality,
hasRecentWebRTCTimeout: getHasRecentWebRTCTimeout(
initialProps.cacheWebRTCFailureMs,
),
});

const initialControls: ControlsState = {
Expand Down Expand Up @@ -562,6 +582,7 @@ export const createControllerStore = ({
backoff: Math.max(initialProps.backoff ?? 500, 100),
backoffMax: Math.max(initialProps.backoffMax ?? 30000, 10000),
clipLength: initialProps.clipLength ?? null,
cacheWebRTCFailureMs: initialProps.cacheWebRTCFailureMs ?? null,
hotkeys: initialProps?.hotkeys ?? true,
jwt: initialProps.jwt ?? null,
lowLatency,
Expand Down Expand Up @@ -592,6 +613,10 @@ export const createControllerStore = ({
poster,
})),

onWebRTCTimeout: () => {
webrtcTimeoutLastTime = Date.now();
},

setAutohide: (autohide) =>
set(({ __controls }) => ({
__controls: {
Expand Down Expand Up @@ -756,6 +781,9 @@ export const createControllerStore = ({
sessionToken: __controls.sessionToken,
src,
videoQuality,
hasRecentWebRTCTimeout: getHasRecentWebRTCTimeout(
__initialProps.cacheWebRTCFailureMs,
),
});

return {
Expand Down Expand Up @@ -1016,6 +1044,10 @@ export const createControllerStore = ({
...sortedSources.slice(0, currentSourceIndex + 1),
];

const hasRecentWebRTCTimeout = getHasRecentWebRTCTimeout(
__initialProps.cacheWebRTCFailureMs,
);

// Function to determine if a source type can be played
const canPlaySourceType = (src: Src) => {
const hasOneWebRTCSource = sortedSources.some(
Expand All @@ -1036,6 +1068,11 @@ export const createControllerStore = ({
return src.type !== "webrtc";
}

// if there was a recent timeout for webrtc, do not play webrtc
if (hasRecentWebRTCTimeout) {
return src.type !== "webrtc";
}

// else play if webrtc is supported
return src.type === "webrtc"
? __device.isWebRTCSupported
Expand Down Expand Up @@ -1087,7 +1124,7 @@ export const createControllerStore = ({
{
name: "livepeer-media-controller",
version: 2,
// since these values are persisted across media, only persist volume, playbackRate, videoQuality
// since these values are persisted across media, only persist volume & videoQuality
partialize: ({ volume, videoQuality }) => ({
volume,
videoQuality,
Expand Down
Loading

0 comments on commit 2045585

Please sign in to comment.