HLS player updates

This commit is contained in:
Isaac 2024-10-15 20:07:21 +04:00
parent 575fa5264d
commit 5959eb29d3
5 changed files with 920 additions and 610 deletions

File diff suppressed because one or more lines are too long

View File

@ -57,6 +57,7 @@ export class SourceBufferStub extends EventTarget {
window.bridgeObjectMap[this.bridgeId] = this; window.bridgeObjectMap[this.bridgeId] = this;
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "constructor", { window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "constructor", {
"mediaSourceId": this.mediaSource.bridgeId,
"mimeType": mimeType "mimeType": mimeType
}); });
} }
@ -146,6 +147,7 @@ export class MediaSourceStub extends EventTarget {
this._duration = NaN; this._duration = NaN;
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "constructor", { window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "constructor", {
"id": this.internalId
}); });
// Simulate asynchronous opening of MediaSource // Simulate asynchronous opening of MediaSource
@ -167,6 +169,12 @@ export class MediaSourceStub extends EventTarget {
const sourceBuffer = new SourceBufferStub(this, mimeType); const sourceBuffer = new SourceBufferStub(this, mimeType);
this.sourceBuffers._add(sourceBuffer); this.sourceBuffers._add(sourceBuffer);
this.activeSourceBuffers._add(sourceBuffer); this.activeSourceBuffers._add(sourceBuffer);
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "updateSourceBuffers", {
"ids": this.sourceBuffers._buffers.map((sb) => sb.bridgeId)
}).then((result) => {
})
return sourceBuffer; return sourceBuffer;
} }
@ -175,6 +183,11 @@ export class MediaSourceStub extends EventTarget {
throw new DOMException('SourceBuffer not found', 'NotFoundError'); throw new DOMException('SourceBuffer not found', 'NotFoundError');
} }
this.activeSourceBuffers._remove(sourceBuffer); this.activeSourceBuffers._remove(sourceBuffer);
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "updateSourceBuffers", {
"ids": this.sourceBuffers._buffers.map((sb) => sb.bridgeId)
}).then((result) => {
})
} }
endOfStream(error) { endOfStream(error) {

View File

@ -2,9 +2,11 @@ import { TimeRangesStub } from "./TimeRangesStub.js"
import { TextTrackStub, TextTrackListStub } from "./TextTrackStub.js" import { TextTrackStub, TextTrackListStub } from "./TextTrackStub.js"
export class VideoElementStub extends EventTarget { export class VideoElementStub extends EventTarget {
constructor() { constructor(id) {
super(); super();
this.instanceId = id;
this.bridgeId = window.nextInternalId; this.bridgeId = window.nextInternalId;
window.nextInternalId += 1; window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this; window.bridgeObjectMap[this.bridgeId] = this;
@ -23,13 +25,14 @@ export class VideoElementStub extends EventTarget {
this.autoplay = false; this.autoplay = false;
this.controls = false; this.controls = false;
this.error = null; this.error = null;
this.src = ''; this._src = '';
this.videoWidth = 0; this.videoWidth = 0;
this.videoHeight = 0; this.videoHeight = 0;
this.textTracks = new TextTrackListStub(); this.textTracks = new TextTrackListStub();
this.isWaiting = false; this.isWaiting = false;
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "constructor", { window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "constructor", {
"instanceId": this.instanceId
}); });
setTimeout(() => { setTimeout(() => {
@ -52,6 +55,7 @@ export class VideoElementStub extends EventTarget {
this.dispatchEvent(new Event('seeking')); this.dispatchEvent(new Event('seeking'));
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setCurrentTime", { window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setCurrentTime", {
"instanceId": this.instanceId,
"currentTime": value "currentTime": value
}).then((result) => { }).then((result) => {
this.dispatchEvent(new Event('seeked')); this.dispatchEvent(new Event('seeked'));
@ -59,6 +63,33 @@ export class VideoElementStub extends EventTarget {
} }
} }
get src() {
return this._src;
}
set src(value) {
this._src = value;
var media = window.mediaSourceMap[this._src];
if (media) {
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setMediaSource", {
"instanceId": this.instanceId,
"mediaSourceId": media.bridgeId
}).then((result) => {
})
}
}
removeAttribute(name) {
if (name === "src") {
this._src = "";
}
}
querySelectorAll(name) {
const fragment = document.createDocumentFragment();
return fragment.querySelectorAll('*');
}
bridgeUpdateBuffered(value) { bridgeUpdateBuffered(value) {
const updatedRanges = value; const updatedRanges = value;
var ranges = []; var ranges = [];
@ -103,6 +134,7 @@ export class VideoElementStub extends EventTarget {
play() { play() {
if (this.paused) { if (this.paused) {
return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "play", { return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "play", {
"instanceId": this.instanceId,
}).then((result) => { }).then((result) => {
this.dispatchEvent(new Event('play')); this.dispatchEvent(new Event('play'));
this.dispatchEvent(new Event('playing')); this.dispatchEvent(new Event('playing'));
@ -118,6 +150,7 @@ export class VideoElementStub extends EventTarget {
this.dispatchEvent(new Event('pause')); this.dispatchEvent(new Event('pause'));
return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "pause", { return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "pause", {
"instanceId": this.instanceId,
}).then((result) => { }).then((result) => {
}) })
} }
@ -131,43 +164,12 @@ export class VideoElementStub extends EventTarget {
return window.mediaSourceMap[this.src]; return window.mediaSourceMap[this.src];
} }
/*_simulateTimeUpdate() {
if (this._isPlaying) {
// Simulate time progression
setTimeout(() => {
var bufferedEnd = 0.0;
const media = this._getMedia();
if (media) {
if (media.sourceBuffers.length != 0) {
this.buffered = media.sourceBuffers._buffers[0].buffered;
bufferedEnd = this.buffered.length == 0 ? 0 : this.buffered.end(this.buffered.length - 1);
}
}
// Consume buffered data
if (this.currentTime < bufferedEnd) {
// Advance currentTime
this._currentTime += 0.1 * this.playbackRate; // Increment currentTime
this.dispatchEvent(new Event('timeupdate'));
// Continue simulation
this._simulateTimeUpdate();
} else {
console.log("Buffer underrun");
// Buffer underrun
this._isPlaying = false;
this.paused = true;
this.dispatchEvent(new Event('waiting'));
// The player should react by buffering more data
}
}, 100);
}
}*/
addTextTrack(kind, label, language) { addTextTrack(kind, label, language) {
const textTrack = new TextTrackStub(kind, label, language); const textTrack = new TextTrackStub(kind, label, language);
this.textTracks._add(textTrack); this.textTracks._add(textTrack);
return textTrack; return textTrack;
} }
load() {
}
} }

View File

@ -40,13 +40,11 @@ export function bridgeInvokeCallback(callbackId, result) {
} }
} }
var useStubs = true;
window.nextInternalId = 0; window.nextInternalId = 0;
window.mediaSourceMap = {}; window.mediaSourceMap = {};
// Replace the global MediaSource with our stub // Replace the global MediaSource with our stub
if (useStubs && typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.MediaSource = MediaSourceStub; window.MediaSource = MediaSourceStub;
window.ManagedMediaSource = MediaSourceStub; window.ManagedMediaSource = MediaSourceStub;
window.SourceBuffer = SourceBufferStub; window.SourceBuffer = SourceBufferStub;
@ -57,44 +55,35 @@ if (useStubs && typeof window !== 'undefined') {
}; };
} }
function postPlayerEvent(id, eventName, eventData) {
function postPlayerEvent(eventName, eventData) {
if (window.webkit && window.webkit.messageHandlers) { if (window.webkit && window.webkit.messageHandlers) {
window.webkit.messageHandlers.performAction.postMessage({'event': eventName, 'data': eventData}); window.webkit.messageHandlers.performAction.postMessage({'instanceId': id, 'event': eventName, 'data': eventData});
} }
};
var video;
var hls;
var isManifestParsed = false;
var isFirstFrameReady = false;
var currentTimeUpdateTimeout = null;
export function playerInitialize(params) {
video.muted = false;
video.addEventListener('loadeddata', (event) => {
if (!isFirstFrameReady) {
isFirstFrameReady = true;
refreshPlayerStatus();
} }
export class HlsPlayerInstance {
constructor(id) {
this.id = id;
this.isManifestParsed = false;
this.currentTimeUpdateTimeout = null;
this.video = new VideoElementStub(this.id);
}
playerInitialize(params) {
this.video.addEventListener("playing", () => {
this.refreshPlayerStatus();
}); });
video.addEventListener("playing", function() { this.video.addEventListener("pause", () => {
refreshPlayerStatus(); this.refreshPlayerStatus();
}); });
video.addEventListener("pause", function() { this.video.addEventListener("seeking", () => {
refreshPlayerStatus(); this.refreshPlayerStatus();
}); });
video.addEventListener("seeking", function() { this.video.addEventListener("waiting", () => {
refreshPlayerStatus(); this.refreshPlayerStatus();
});
video.addEventListener("waiting", function() {
refreshPlayerStatus();
}); });
hls = new Hls({ this.hls = new Hls({
startLevel: 0, startLevel: 0,
testBandwidth: false, testBandwidth: false,
debug: params['debug'] || true, debug: params['debug'] || true,
@ -105,59 +94,59 @@ export function playerInitialize(params) {
maxFragLookUpTolerance: 0.001, maxFragLookUpTolerance: 0.001,
nudgeMaxRetry: 10000 nudgeMaxRetry: 10000
}); });
hls.on(Hls.Events.MANIFEST_PARSED, function() { this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
isManifestParsed = true; this.isManifestParsed = true;
refreshPlayerStatus(); this.refreshPlayerStatus();
}); });
hls.on(Hls.Events.LEVEL_SWITCHED, function() { this.hls.on(Hls.Events.LEVEL_SWITCHED, () => {
refreshPlayerStatus(); this.refreshPlayerStatus();
}); });
hls.on(Hls.Events.LEVELS_UPDATED, function() { this.hls.on(Hls.Events.LEVELS_UPDATED, () => {
refreshPlayerStatus(); this.refreshPlayerStatus();
}); });
hls.loadSource('master.m3u8'); this.hls.loadSource(params["urlPrefix"] + "master.m3u8");
hls.attachMedia(video); this.hls.attachMedia(this.video);
} }
export function playerLoad(initialLevelIndex) { playerLoad(initialLevelIndex) {
hls.startLevel = initialLevelIndex; this.hls.startLevel = initialLevelIndex;
hls.startLoad(-1, false); this.hls.startLoad(-1, false);
} }
export function playerPlay() { playerPlay() {
video.play(); this.video.play();
} }
export function playerPause() { playerPause() {
video.pause(); this.video.pause();
} }
export function playerSetBaseRate(value) { playerSetBaseRate(value) {
video.playbackRate = value; this.video.playbackRate = value;
} }
export function playerSetLevel(level) { playerSetLevel(level) {
if (level >= 0) { if (level >= 0) {
hls.currentLevel = level; this.hls.currentLevel = level;
} else { } else {
hls.currentLevel = -1; this.hls.currentLevel = -1;
} }
} }
export function playerSeek(value) { playerSeek(value) {
video.currentTime = value; this.video.currentTime = value;
} }
export function playerSetIsMuted(value) { playerSetIsMuted(value) {
video.muted = value; this.video.muted = value;
} }
function getLevels() { getLevels() {
var levels = []; var levels = [];
for (var i = 0; i < hls.levels.length; i++) { for (var i = 0; i < this.hls.levels.length; i++) {
var level = hls.levels[i]; var level = this.hls.levels[i];
levels.push({ levels.push({
'index': i, 'index': i,
'bitrate': level.bitrate || 0, 'bitrate': level.bitrate || 0,
@ -168,67 +157,69 @@ function getLevels() {
return levels; return levels;
} }
function refreshPlayerStatus() { refreshPlayerStatus() {
var isPlaying = false; var isPlaying = false;
if (!video.paused && !video.ended && video.readyState > 2) { if (!this.video.paused && !this.video.ended && this.video.readyState > 2) {
isPlaying = true; isPlaying = true;
} }
postPlayerEvent('playerStatus', { postPlayerEvent(this.id, 'playerStatus', {
'isReady': isManifestParsed, 'isReady': this.isManifestParsed,
'isFirstFrameReady': isFirstFrameReady, 'isPlaying': !this.video.paused,
'isPlaying': !video.paused, 'rate': isPlaying ? this.video.playbackRate : 0.0,
'rate': isPlaying ? video.playbackRate : 0.0, 'defaultRate': this.video.playbackRate,
'defaultRate': video.playbackRate, 'levels': this.getLevels(),
'levels': getLevels(), 'currentLevel': this.hls.currentLevel
'currentLevel': hls.currentLevel
}); });
refreshPlayerCurrentTime(); this.refreshPlayerCurrentTime();
if (isPlaying) { if (isPlaying) {
if (currentTimeUpdateTimeout == null) { if (this.currentTimeUpdateTimeout == null) {
currentTimeUpdateTimeout = setTimeout(() => { this.currentTimeUpdateTimeout = setTimeout(() => {
refreshPlayerCurrentTime(); this.refreshPlayerCurrentTime();
}, 200); }, 200);
} }
} else { } else {
if(currentTimeUpdateTimeout != null){ if(this.currentTimeUpdateTimeout != null){
clearTimeout(currentTimeUpdateTimeout); clearTimeout(this.currentTimeUpdateTimeout);
currentTimeUpdateTimeout = null; this.currentTimeUpdateTimeout = null;
} }
} }
} }
function refreshPlayerCurrentTime() { refreshPlayerCurrentTime() {
postPlayerEvent('playerCurrentTime', { postPlayerEvent(this.id, 'playerCurrentTime', {
'value': video.currentTime 'value': this.video.currentTime
}); });
currentTimeUpdateTimeout = setTimeout(() => { this.currentTimeUpdateTimeout = setTimeout(() => {
refreshPlayerCurrentTime() this.refreshPlayerCurrentTime()
}, 200); }, 200);
} }
}
window.invokeOnLoad = function() {
postPlayerEvent(this.id, 'windowOnLoad', {
});
}
window.onload = () => { window.onload = () => {
if (useStubs) { window.invokeOnLoad();
video = new VideoElementStub();
} else {
video = document.createElement('video');
video.playsInline = true;
video.controls = true;
document.body.appendChild(video);
}
postPlayerEvent('windowOnLoad', {
});
}; };
window.playerInitialize = playerInitialize; window.hlsPlayer_instances = {};
window.playerLoad = playerLoad;
window.playerPlay = playerPlay; window.hlsPlayer_makeInstance = function(id) {
window.playerPause = playerPause; window.hlsPlayer_instances[id] = new HlsPlayerInstance(id);
window.playerSetBaseRate = playerSetBaseRate; }
window.playerSetLevel = playerSetLevel;
window.playerSeek = playerSeek; window.hlsPlayer_destroyInstance = function(id) {
window.playerSetIsMuted = playerSetIsMuted; const instance = window.hlsPlayer_instances[id];
if (instance) {
delete window.hlsPlayer_instances[id];
instance.video.pause();
instance.hls.destroy();
}
}
window.bridgeInvokeCallback = bridgeInvokeCallback; window.bridgeInvokeCallback = bridgeInvokeCallback;