Skip to content

Commit

Permalink
Live stream delay. (#318)
Browse files Browse the repository at this point in the history
* Fix issue with segment index.

* Add try catch block to hook segment index method.

* Fix bug.

* Fix bug.

* Multiply to 2 to get segment id from start time.

* Substitute segment index get method immediately if it already exists.

* Set live edge delay.

* Use more common configuration approach for shaka-player.

* Rename method.

---------

Co-authored-by: Igor Zolotarenko <[email protected]>
  • Loading branch information
i-zolotarenko and i-zolotarenko authored Dec 27, 2023
1 parent a1b80ae commit 2fd5fb0
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 133 deletions.
39 changes: 15 additions & 24 deletions p2p-media-loader-demo/index.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite + React + TS</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/clappr@latest"
></script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/gh/clappr/clappr-level-selector-plugin@latest/dist/level-selector.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/shaka-player@latest/dist/shaka-player.compiled.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/gh/clappr/dash-shaka-playback@latest/dist/dash-shaka-playback.external.js"
></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/clappr@latest"></script>
<script type="text/javascript"
src="https://cdn.jsdelivr.net/gh/clappr/clappr-level-selector-plugin@latest/dist/level-selector.min.js"></script>
<script type="text/javascript"
src="https://cdn.jsdelivr.net/npm/shaka-player@~4.6.0/dist/shaka-player.compiled.min.js"></script>
<script type="text/javascript"
src="https://cdn.jsdelivr.net/gh/clappr/dash-shaka-playback@latest/dist/dash-shaka-playback.external.js"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
30 changes: 15 additions & 15 deletions p2p-media-loader-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,27 @@ const streamUrls = {
hlsBigBunnyBuck: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
hlsByteRangeVideo:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8",
hlsBasicExample:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
hlsAdvancedVideo:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8",
hlsAdvancedVideo2:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8",
hlsAdvancedVideo3:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8",
hlsAdvancedVideo4:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8",
hlsBasicExample:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
hlsLive1:
"https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.DmumNckWFTqz.m3u8",
hlsLive2:
"https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8",
live2OnlyLevel4:
hlsLive2Level4Only:
"https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/level_4.m3u8",
audioOnly:
hlsAudioOnly:
"https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/a1/prog_index.m3u8",
bigBunnyBuckDash: "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd",
dashLiveBigBunnyBuck:
"https://livesim.dashif.org/livesim/testpic_2s/Manifest.mpd",
dashVODBigBunnyBuck:
"https://dash.akamaized.net/dash264/TestCases/5b/nomor/6.mpd",
dashLiveHokey:
"https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams/4577dca5f8a44756875ab5cc913cd1f1/index.mpd",
};

function App() {
Expand Down Expand Up @@ -141,10 +139,7 @@ function App() {
type: "customHls",
customType: {
customHls: (video: HTMLVideoElement) => {
const hls = new window.Hls({
...engine.getConfig(),
liveSyncDurationCount: 7,
});
const hls = new window.Hls(engine.getConfig());
engine.setHls(hls);
hls.loadSource(video.src);
hls.attachMedia(video);
Expand Down Expand Up @@ -197,7 +192,7 @@ function App() {
shakaPlayer.addEventListener("error", (event: { code: number }) => {
onError(event);
});
engine.initShakaPlayer(shakaPlayer);
engine.configureAndInitShakaPlayer(shakaPlayer);
shakaPlayer.load(src).catch(onError);

shakaInstance.current = shakaPlayer;
Expand All @@ -220,7 +215,7 @@ function App() {
player.addEventListener("error", (event: { detail: { code: unknown } }) => {
onError(event.detail);
});
engine.initShakaPlayer(player);
engine.configureAndInitShakaPlayer(player);
player.load(url).catch(onError);
shakaInstance.current = player;
setPlayerToWindow(player);
Expand All @@ -235,7 +230,7 @@ function App() {
source: url,
plugins: [window.DashShakaPlayback, window.LevelSelector],
shakaOnBeforeLoad: (shakaPlayerInstance: any) => {
engine.initShakaPlayer(shakaPlayerInstance);
engine.configureAndInitShakaPlayer(shakaPlayerInstance);
},
});
setPlayerToWindow(clapprPlayer);
Expand Down Expand Up @@ -302,6 +297,10 @@ function App() {
}
};

const createInNewTab = () => {
window.open(window.location.href, "_blank");
};

return (
<div style={{ width: 1000, margin: "auto" }}>
<div style={{ textAlign: "center" }}>
Expand Down Expand Up @@ -338,6 +337,7 @@ function App() {
<button onClick={loadStreamWithExistingInstance}>
Load stream with existing hls/shaka instance
</button>
<button onClick={createInNewTab}>Create in new tab</button>
</div>
</div>
<div style={{ display: "flex", justifyContent: "center" }}>
Expand Down
7 changes: 6 additions & 1 deletion packages/p2p-media-loader-core/src/hybrid-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ export class HybridLoader {
if (statuses.isHighDemand) {
if (request?.type === "http" && request.status === "loading") continue;

const isP2PLoadingRequest =
request?.status === "loading" && request.type === "p2p";

if (this.requests.executingHttpCount < simultaneousHttpDownloads) {
if (isP2PLoadingRequest) request.abortFromEngine();
void this.loadThroughHttp(segment);
continue;
}
Expand All @@ -196,11 +200,12 @@ export class HybridLoader {
this.abortLastHttpLoadingInQueueAfterItem(queue, segment) &&
this.requests.executingHttpCount < simultaneousHttpDownloads
) {
if (isP2PLoadingRequest) request.abortFromEngine();
void this.loadThroughHttp(segment);
continue;
}

if (request?.type === "p2p" && request.status === "loading") continue;
if (isP2PLoadingRequest) continue;

if (this.requests.executingP2PCount < simultaneousP2PDownloads) {
void this.loadThroughP2P(segment);
Expand Down
4 changes: 3 additions & 1 deletion packages/p2p-media-loader-core/src/p2p/tracker-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ export class P2PTrackerClient {
},
this.settings
);
this.logger(`connected with peer: ${peerItem.peer.id}`);
this.logger(
`connected with peer: ${peerItem.peer.id} ${this.streamShortId}`
);
this.eventHandlers.onPeerConnected(peerItem.peer);
});
};
Expand Down
5 changes: 5 additions & 0 deletions packages/p2p-media-loader-core/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ export class Request {
abortFromProcessQueue() {
this.throwErrorIfNotLoadingStatus();
this._status = "aborted";
this.logger(
`${this.currentAttempt?.type} ${this.segment.externalId} aborted`
);
this._abortRequestCallback?.("abort");
this._abortRequestCallback = undefined;
this.currentAttempt = undefined;
Expand All @@ -205,6 +208,7 @@ export class Request {
this._status = "failed";
const error = new RequestError("bytes-receiving-timeout");
this._abortRequestCallback?.(error.type);
this.logger(`${this.type} ${this.segment.externalId} failed ${error.type}`);

this.currentAttempt.error = error;
this._failedAttempts.push(this.currentAttempt);
Expand All @@ -217,6 +221,7 @@ export class Request {
if (!this.currentAttempt) return;

this._status = "failed";
this.logger(`${this.type} ${this.segment.externalId} failed ${error.type}`);
this.currentAttempt.error = error;
this._failedAttempts.push(this.currentAttempt);
this.notReceivingBytesTimeout.clear();
Expand Down
7 changes: 0 additions & 7 deletions packages/p2p-media-loader-core/src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Segment, Stream } from "../types";
import { QueueItem } from "./queue";
import { SegmentPlaybackStatuses } from "./stream";

export function getStreamString(stream: Stream) {
Expand All @@ -21,9 +20,3 @@ export function getSegmentPlaybackStatusesString(
if (isP2PDownloadable) return "p2p-window";
return "-";
}

export function getQueueItemString(item: QueueItem) {
const { segment, statuses } = item;
const statusString = getSegmentPlaybackStatusesString(statuses);
return `${segment.externalId} ${statusString}`;
}
16 changes: 12 additions & 4 deletions packages/p2p-media-loader-core/src/utils/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,25 @@ export function getSegmentPlaybackStatuses(
} = timeWindowsSettings;

return {
isHighDemand: isInTimeWindow(segment, playback, highDemandTimeWindow),
isHttpDownloadable: isInTimeWindow(
isHighDemand: isSegmentInTimeWindow(
segment,
playback,
highDemandTimeWindow
),
isHttpDownloadable: isSegmentInTimeWindow(
segment,
playback,
httpDownloadTimeWindow
),
isP2PDownloadable: isInTimeWindow(segment, playback, p2pDownloadTimeWindow),
isP2PDownloadable: isSegmentInTimeWindow(
segment,
playback,
p2pDownloadTimeWindow
),
};
}

function isInTimeWindow(
function isSegmentInTimeWindow(
segment: Segment,
playback: Playback,
timeWindowLength: number
Expand Down
6 changes: 4 additions & 2 deletions packages/p2p-media-loader-hlsjs/src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { PlaylistLoaderBase } from "./playlist-loader";
import { SegmentManager } from "./segment-mananger";
import { Core, CoreEventHandlers } from "p2p-media-loader-core";

const LIVE_EDGE_DELAY = 25;

export class Engine {
private readonly core: Core;
private readonly segmentManager: SegmentManager;
Expand All @@ -23,12 +25,12 @@ export class Engine {

public getConfig(): Pick<
HlsConfig,
"fLoader" | "pLoader" | "liveSyncDurationCount"
"fLoader" | "pLoader" | "liveSyncDuration"
> {
return {
liveSyncDurationCount: 7,
fLoader: this.createFragmentLoaderClass(),
pLoader: this.createPlaylistLoaderClass(),
liveSyncDuration: LIVE_EDGE_DELAY,
};
}

Expand Down
20 changes: 11 additions & 9 deletions packages/p2p-media-loader-shaka/src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
import { Loader } from "./loading-handler";
import { Core, CoreEventHandlers } from "p2p-media-loader-core";

const LIVE_EDGE_DELAY = 25;

export class Engine {
private player?: shaka.Player;
private readonly shaka: Shaka;
Expand All @@ -29,20 +31,24 @@ export class Engine {
this.segmentManager = new SegmentManager(this.streamInfo, this.core);
}

initShakaPlayer(player: shaka.Player) {
configureAndInitShakaPlayer(player: shaka.Player) {
if (this.player === player) return;
if (this.player) this.destroy();
this.player = player;
this.player.configure("manifest.defaultPresentationDelay", LIVE_EDGE_DELAY);
this.player.configure(
"manifest.dash.ignoreSuggestedPresentationDelay",
true
);
this.updatePlayerEventHandlers("register");
}

private updatePlayerEventHandlers = (type: "register" | "unregister") => {
const { player } = this;
if (!player) return;

if (!this.player) return;
const networkingEngine =
this.player.getNetworkingEngine() as HookedNetworkingEngine | null;
player.getNetworkingEngine() as HookedNetworkingEngine | null;
if (networkingEngine) {
if (type === "register") {
const p2pml: P2PMLShakaData = {
Expand Down Expand Up @@ -78,6 +84,7 @@ export class Engine {
this.destroyCurrentStreamContext();
this.updateMediaElementEventHandlers("unregister");
};

private destroyCurrentStreamContext = () => {
this.streamInfo.protocol = undefined;
this.streamInfo.manifestResponseUrl = undefined;
Expand Down Expand Up @@ -140,12 +147,7 @@ export class Engine {
const { p2pml } = request;
if (!p2pml) return shaka.net.HttpFetchPlugin.parse(...args);

const loader = new Loader(
p2pml.shaka,
p2pml.core,
p2pml.streamInfo,
p2pml.segmentManager
);
const loader = new Loader(p2pml.shaka, p2pml.core, p2pml.streamInfo);
return loader.load(...args);
};
NetworkingEngine.registerScheme("http", handleLoading);
Expand Down
4 changes: 1 addition & 3 deletions packages/p2p-media-loader-shaka/src/loading-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as Utils from "./stream-utils";
import { SegmentManager } from "./segment-manager";
import { StreamInfo } from "./types";
import { Shaka, Stream } from "./types";
import {
Expand All @@ -19,8 +18,7 @@ export class Loader {
constructor(
private readonly shaka: Shaka,
private readonly core: Core<Stream>,
readonly streamInfo: StreamInfo,
private readonly segmentManager: SegmentManager
readonly streamInfo: StreamInfo
) {}

private defaultLoad() {
Expand Down
Loading

0 comments on commit 2fd5fb0

Please sign in to comment.