Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,28 @@
# Node modules
node_modules/
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Webpack and build artifacts
/dist
/build
# Environment files
.env
.env.local
.env.*.local
# OS generated
.DS_Store
Thumbs.db
@@ -0,0 +1,8 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
]
}
@@ -0,0 +1,7 @@
#!/bin/sh
mkdir -p ../HlsBundle
rm -rf ../HlsBundle/index
mkdir ../HlsBundle/index
npm run build-$1
cp ./dist/* ../HlsBundle/index/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
{
"name": "myhls",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build-development": "webpack --config webpack.dev.js",
"build-release": "webpack --config webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"expose-loader": "^5.0.0",
"express": "^4.18.2",
"html-webpack-plugin": "^5.5.3",
"style-loader": "^3.3.3",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-middleware": "^6.1.1",
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^6.0.1"
},
"dependencies": {
"base-64": "^1.0.0",
"event-target-polyfill": "^0.0.4",
"hls.js": "^1.5.15"
}
}
@@ -0,0 +1,20 @@
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
@@ -0,0 +1,240 @@
import { TimeRangesStub } from "./TimeRangesStub.js"
function bytesToBase64(bytes) {
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte),
).join("");
return btoa(binString);
}
export class SourceBufferListStub extends EventTarget {
constructor() {
super();
this._buffers = [];
}
_add(buffer) {
this._buffers.push(buffer);
this.dispatchEvent(new Event('addsourcebuffer'));
}
_remove(buffer) {
const index = this._buffers.indexOf(buffer);
if (index === -1) {
return false;
}
this._buffers.splice(index, 1);
this.dispatchEvent(new Event('removesourcebuffer'));
return true;
}
get length() {
return this._buffers.length;
}
item(index) {
return this._buffers[index];
}
[Symbol.iterator]() {
return this._buffers[Symbol.iterator]();
}
}
export class SourceBufferStub extends EventTarget {
constructor(mediaSource, mimeType) {
super();
this.mediaSource = mediaSource;
this.mimeType = mimeType;
this.updating = false;
this.buffered = new TimeRangesStub();
this.timestampOffset = 0;
this.appendWindowStart = 0;
this.appendWindowEnd = Infinity;
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this;
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "constructor", {
"mediaSourceId": this.mediaSource.bridgeId,
"mimeType": mimeType
});
}
appendBuffer(data) {
if (this.updating) {
throw new DOMException('SourceBuffer is updating', 'InvalidStateError');
}
this.updating = true;
this.dispatchEvent(new Event('updatestart'));
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "appendBuffer", {
"data": bytesToBase64(data)
}).then((result) => {
const updatedRanges = result["ranges"];
var ranges = [];
for (var i = 0; i < updatedRanges.length; i += 2) {
ranges.push({
start: updatedRanges[i],
end: updatedRanges[i + 1]
});
}
this.buffered._ranges = ranges;
this.mediaSource._reopen();
this.mediaSource.emitUpdatedBuffer();
this.updating = false;
this.dispatchEvent(new Event('update'));
this.dispatchEvent(new Event('updateend'));
});
}
abort() {
if (this.updating) {
this.updating = false;
this.dispatchEvent(new Event('abort'));
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "abort", {}).then((result) => {
});
}
}
remove(start, end) {
if (this.updating) {
throw new DOMException('SourceBuffer is updating', 'InvalidStateError');
}
this.updating = true;
this.dispatchEvent(new Event('updatestart'));
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "remove", {
"start": start,
"end": end
}).then((result) => {
const updatedRanges = result["ranges"];
var ranges = [];
for (var i = 0; i < updatedRanges.length; i += 2) {
ranges.push({
start: updatedRanges[i],
end: updatedRanges[i + 1]
});
}
this.buffered._ranges = ranges;
this.mediaSource._reopen();
this.mediaSource.emitUpdatedBuffer();
this.updating = false;
this.dispatchEvent(new Event('update'));
this.dispatchEvent(new Event('updateend'));
});
}
}
export class MediaSourceStub extends EventTarget {
constructor() {
super();
this.internalId = window.nextInternalId;
window.nextInternalId += 1;
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this;
this.sourceBuffers = new SourceBufferListStub();
this.activeSourceBuffers = new SourceBufferListStub();
this.readyState = 'closed';
this._duration = NaN;
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "constructor", {
"id": this.internalId
});
// Simulate asynchronous opening of MediaSource
setTimeout(() => {
this.readyState = 'open';
this.dispatchEvent(new Event('sourceopen'));
}, 0);
}
static isTypeSupported(mimeType) {
// Assume all MIME types are supported in this stub
return true;
}
emitUpdatedBuffer() {
this.dispatchEvent(new Event("bufferChanged"));
}
getBufferedRanges() {
if (this.sourceBuffers._buffers.length != 0) {
return this.sourceBuffers._buffers[0].buffered._ranges;
}
return [];
}
addSourceBuffer(mimeType) {
if (this.readyState !== 'open') {
throw new DOMException('MediaSource is not open', 'InvalidStateError');
}
const sourceBuffer = new SourceBufferStub(this, mimeType);
this.sourceBuffers._add(sourceBuffer);
this.activeSourceBuffers._add(sourceBuffer);
this.dispatchEvent(new Event("bufferChanged"));
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "updateSourceBuffers", {
"ids": this.sourceBuffers._buffers.map((sb) => sb.bridgeId)
}).then((result) => {
})
return sourceBuffer;
}
removeSourceBuffer(sourceBuffer) {
if (!this.sourceBuffers._remove(sourceBuffer)) {
throw new DOMException('SourceBuffer not found', 'NotFoundError');
}
this.activeSourceBuffers._remove(sourceBuffer);
this.dispatchEvent(new Event("bufferChanged"));
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "updateSourceBuffers", {
"ids": this.sourceBuffers._buffers.map((sb) => sb.bridgeId)
}).then((result) => {
})
}
endOfStream(error) {
if (this.readyState !== 'open') {
throw new DOMException('MediaSource is not open', 'InvalidStateError');
}
this.readyState = 'ended';
this.dispatchEvent(new Event('sourceended'));
}
_reopen() {
if (this.readyState !== 'open') {
this.readyState = 'open';
this.dispatchEvent(new Event('sourceopen'));
}
}
set duration(value) {
if (this.readyState === 'closed') {
throw new DOMException('MediaSource is closed', 'InvalidStateError');
}
this._duration = value;
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "setDuration", {
"duration": value
}).then((result) => {
})
}
get duration() {
return this._duration;
}
}
@@ -0,0 +1,85 @@
export class TextTrackStub extends EventTarget {
constructor(kind = '', label = '', language = '') {
super();
this.kind = kind;
this.label = label;
this.language = language;
this.mode = 'disabled'; // 'disabled', 'hidden', or 'showing'
this.cues = new TextTrackCueListStub();
this.activeCues = new TextTrackCueListStub();
}
addCue(cue) {
this.cues._add(cue);
}
removeCue(cue) {
this.cues._remove(cue);
}
}
export class TextTrackCueListStub {
constructor() {
this._cues = [];
}
get length() {
return this._cues.length;
}
item(index) {
return this._cues[index];
}
getCueById(id) {
return this._cues.find(cue => cue.id === id) || null;
}
_add(cue) {
this._cues.push(cue);
}
_remove(cue) {
const index = this._cues.indexOf(cue);
if (index !== -1) {
this._cues.splice(index, 1);
}
}
[Symbol.iterator]() {
return this._cues[Symbol.iterator]();
}
}
export class TextTrackListStub extends EventTarget {
constructor() {
super();
this._tracks = [];
}
get length() {
return this._tracks.length;
}
item(index) {
return this._tracks[index];
}
_add(track) {
this._tracks.push(track);
this.dispatchEvent(new Event('addtrack'));
}
_remove(track) {
const index = this._tracks.indexOf(track);
if (index !== -1) {
this._tracks.splice(index, 1);
this.dispatchEvent(new Event('removetrack'));
}
}
[Symbol.iterator]() {
return this._tracks[Symbol.iterator]();
}
}
@@ -0,0 +1,74 @@
export class TimeRangesStub {
constructor() {
this._ranges = [];
}
get length() {
return this._ranges.length;
}
start(index) {
if (index < 0 || index >= this._ranges.length) {
throw new DOMException('Invalid index', 'IndexSizeError');
}
return this._ranges[index].start;
}
end(index) {
if (index < 0 || index >= this._ranges.length) {
throw new DOMException('Invalid index', 'IndexSizeError');
}
return this._ranges[index].end;
}
// Helper method to add a range
_addRange(start, end) {
this._ranges.push({ start, end });
this._normalizeRanges();
}
// Helper method to remove ranges that overlap with a given range
_removeRange(start, end) {
let updatedRanges = [];
for (let range of this._ranges) {
if (range.end <= start || range.start >= end) {
// No overlap, keep the range as is
updatedRanges.push(range);
} else if (range.start < start && range.end > end) {
// The range fully covers the removal range, split into two ranges
updatedRanges.push({ start: range.start, end: start });
updatedRanges.push({ start: end, end: range.end });
} else if (range.start >= start && range.end <= end) {
// The range is entirely within the removal range, remove it
// Do not add to updatedRanges
} else if (range.start < start && range.end > start && range.end <= end) {
// The range overlaps with the removal range on the left
updatedRanges.push({ start: range.start, end: start });
} else if (range.start >= start && range.start < end && range.end > end) {
// The range overlaps with the removal range on the right
updatedRanges.push({ start: end, end: range.end });
}
}
this._ranges = updatedRanges;
}
// Normalize and merge overlapping ranges
_normalizeRanges() {
this._ranges.sort((a, b) => a.start - b.start);
let normalized = [];
for (let range of this._ranges) {
if (normalized.length === 0) {
normalized.push(range);
} else {
let last = normalized[normalized.length - 1];
if (range.start <= last.end) {
last.end = Math.max(last.end, range.end);
} else {
normalized.push(range);
}
}
}
this._ranges = normalized;
}
}
@@ -0,0 +1,203 @@
import { TimeRangesStub } from "./TimeRangesStub.js"
import { TextTrackStub, TextTrackListStub } from "./TextTrackStub.js"
export class VideoElementStub extends EventTarget {
constructor(id) {
super();
this.instanceId = id;
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this;
this._currentTime = 0.0;
this.duration = NaN;
this.paused = true;
this._playbackRate = 1.0;
this.volume = 1.0;
this.muted = false;
this.readyState = 0;
this.networkState = 0;
this.buffered = new TimeRangesStub();
this.seeking = false;
this.loop = false;
this.autoplay = false;
this.controls = false;
this.error = null;
this._src = '';
this.videoWidth = 0;
this.videoHeight = 0;
this.textTracks = new TextTrackListStub();
this.isWaiting = false;
this.currentMedia = null;
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "constructor", {
"instanceId": this.instanceId
});
setTimeout(() => {
this.readyState = 4; // HAVE_ENOUGH_DATA
this.dispatchEvent(new Event('loadedmetadata'));
this.dispatchEvent(new Event('loadeddata'));
this.dispatchEvent(new Event('canplay'));
this.dispatchEvent(new Event('canplaythrough'));
}, 0);
}
get currentTime() {
return this._currentTime;
}
set currentTime(value) {
if (this._currentTime != value) {
this._currentTime = value;
this.dispatchEvent(new Event('seeking'));
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setCurrentTime", {
"instanceId": this.instanceId,
"currentTime": value
}).then((result) => {
this.dispatchEvent(new Event('seeked'));
})
}
}
get playbackRate() {
return this._playbackRate;
}
set playbackRate(value) {
this._playbackRate = value;
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setPlaybackRate", {
"instanceId": this.instanceId,
"playbackRate": value
}).then((result) => {
})
}
get src() {
return this._src;
}
set src(value) {
if (this.currentMedia) {
this.currentMedia.removeEventListener("bufferChanged", false);
}
this._src = value;
var media = window.mediaSourceMap[this._src];
this.currentMedia = media;
if (media) {
media.addEventListener("bufferChanged", () => {
this.updateBufferedFromMediaSource();
}, false);
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setMediaSource", {
"instanceId": this.instanceId,
"mediaSourceId": media.bridgeId
}).then((result) => {
})
}
}
removeAttribute(name) {
if (name === "src") {
this._src = "";
}
}
querySelectorAll(name) {
if (global.isJsCore) {
return [];
} else {
const fragment = document.createDocumentFragment();
return fragment.querySelectorAll('*');
}
}
removeChild(child) {
}
updateBufferedFromMediaSource() {
var currentMedia = this.currentMedia;
if (currentMedia) {
this.buffered._ranges = currentMedia.getBufferedRanges();
} else {
this.buffered._ranges = [];
}
}
bridgeUpdateStatus(dict) {
var paused = !dict["isPlaying"];
var isWaiting = dict["isWaiting"];
var currentTime = dict["currentTime"];
if (this.paused != paused) {
this.paused = paused;
if (paused) {
this.dispatchEvent(new Event('pause'));
} else {
this.dispatchEvent(new Event('play'));
this.dispatchEvent(new Event('playing'));
}
}
if (this.isWaiting != isWaiting) {
this.isWaiting = isWaiting;
if (isWaiting) {
this.dispatchEvent(new Event('waiting'));
}
}
if (this._currentTime != currentTime) {
this._currentTime = currentTime;
this.dispatchEvent(new Event('timeupdate'));
}
}
play() {
if (this.paused) {
return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "play", {
"instanceId": this.instanceId,
}).then((result) => {
this.dispatchEvent(new Event('play'));
this.dispatchEvent(new Event('playing'));
})
} else {
return Promise.resolve();
}
}
pause() {
if (!this.paused) {
this.paused = true;
this.dispatchEvent(new Event('pause'));
return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "pause", {
"instanceId": this.instanceId,
}).then((result) => {
})
}
}
canPlayType(type) {
return 'probably';
}
addTextTrack(kind, label, language) {
const textTrack = new TextTrackStub(kind, label, language);
this.textTracks._add(textTrack);
return textTrack;
}
load() {
}
notifySeeked() {
this.dispatchEvent(new Event('seeking'));
this.dispatchEvent(new Event('seeked'));
}
}
@@ -0,0 +1,150 @@
function base64ToArrayBuffer(base64) {
var binaryString = atob(base64);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
export class XMLHttpRequestStub extends EventTarget {
constructor() {
super();
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
this.readyState = 0;
this.status = 0;
this.statusText = "";
this.responseText = "";
this.responseXML = null;
this._responseData = null;
this.onreadystatechange = null;
this._requestHeaders = {};
this._responseHeaders = {};
this._method = "";
this._url = "";
this._async = true;
this._user = null;
this._password = null;
this._responseType = "";
}
open(method, url, async = true, user = null, password = null) {
this._method = method;
this._url = url;
this._async = async;
this._user = user;
this._password = password;
this.readyState = 1; // Opened
this._triggerReadyStateChange();
}
setRequestHeader(header, value) {
this._requestHeaders[header] = value;
}
getResponseHeader(header) {
return this._responseHeaders[header.toLowerCase()] || null;
}
getAllResponseHeaders() {
return Object.entries(this._responseHeaders)
.map(([header, value]) => `${header}: ${value}`)
.join('\r\n');
}
send(body = null) {
this.readyState = 2;
this._triggerReadyStateChange();
this.readyState = 3; // Loading
this._triggerReadyStateChange();
this.dispatchEvent(new Event("loadstart"));
window.bridgeInvokeAsync(this.bridgeId, "XMLHttpRequest", "load", {
"id": this.bridgeId,
"url": this._url,
"requestHeaders": this._requestHeaders
}).then((result) => {
if (result["error"]) {
this.dispatchEvent(new Event("error"));
} else {
this.status = result["status"];
this.statusText = result["statusText"];
if (result["responseData"]) {
if (this._responseType === "arraybuffer") {
this._responseData = base64ToArrayBuffer(result["responseData"]);
} else {
this.responseText = atob(result["responseData"]);
}
this.responseXML = null;
} else {
this.response = null;
this.responseText = result["responseText"] || null;
this.responseXML = result["responseXML"] || null;
}
this._responseHeaders = result["responseHeaders"];
this.readyState = 4; // Done
this._triggerReadyStateChange();
this.dispatchEvent(new Event("load"));
}
this.dispatchEvent(new Event("loadend"));
});
}
abort() {
this.dispatchEvent(new Event("abort"));
window.bridgeInvokeAsync(this.bridgeId, "XMLHttpRequest", "abort", {
"id": this.bridgeId
});
this.readyState = 0;
this.status = 0;
this.statusText = '';
this.responseText = '';
this.responseXML = null;
this._responseHeaders = {};
this._triggerReadyStateChange();
}
overrideMimeType(mime) {
}
set responseType(type) {
this._responseType = type;
}
get responseType() {
return this._responseType;
}
get response() {
if (this._responseType === '' || this._responseType === 'text') {
return this.responseText;
}
return this._responseData;
}
_triggerReadyStateChange() {
this.dispatchEvent(new Event('readystatechange'));
if (typeof this.onreadystatechange === 'function') {
this.onreadystatechange();
}
}
// Additional methods to simulate responses
_setResponse(status, statusText, responseText, responseHeaders = {}) {
this.status = status;
this.statusText = statusText;
this.responseText = responseText;
this._responseHeaders = responseHeaders;
}
}
@@ -0,0 +1,316 @@
import "event-target-polyfill";
import {decode, encode} from "base-64";
import { VideoElementStub } from "./VideoElementStub.js"
import { MediaSourceStub, SourceBufferStub } from "./MediaSourceStub.js"
import { XMLHttpRequestStub } from "./XMLHttpRequestStub.js"
global.isJsCore = false;
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob) {
global.atob = decode;
}
if (typeof window === 'undefined') {
global.isJsCore = true;
global.navigator = {
userAgent: "Telegram"
};
global.now = function() {
return _JsCorePolyfills.performanceNow();
};
global.window = {
};
global.URL = {
};
window.webkit = {
};
window.webkit.messageHandlers = {
};
window.webkit.messageHandlers.performAction = {
};
window.webkit.messageHandlers.performAction.postMessage = function(dict) {
_JsCorePolyfills.postMessage(dict);
};
global.self.location = {
href: "http://127.0.0.1"
};
global.self.setTimeout = global.setTimeout;
global.self.setInterval = global.setInterval;
global.self.clearTimeout = global.clearTimeout;
global.self.clearInterval = global.clearTimeout;
global.self.URL = global.URL;
global.self.Date = global.Date;
}
import Hls from "hls.js";
window.bridgeObjectMap = {};
window.bridgeCallbackMap = {};
function bridgeInvokeAsync(bridgeId, className, methodName, params) {
var promiseResolve;
var promiseReject;
var result = new Promise(function(resolve, reject) {
promiseResolve = resolve;
promiseReject = reject;
});
const callbackId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeCallbackMap[callbackId] = promiseResolve;
if (window.webkit.messageHandlers) {
window.webkit.messageHandlers.performAction.postMessage({
'event': 'bridgeInvoke',
'data': {
'bridgeId': bridgeId,
'className': className,
'methodName': methodName,
'params': params,
'callbackId': callbackId
}
});
}
return result;
}
window.bridgeInvokeAsync = bridgeInvokeAsync
export function bridgeInvokeCallback(callbackId, result) {
const callback = window.bridgeCallbackMap[callbackId];
if (callback) {
callback(result);
}
}
window.nextInternalId = 0;
window.mediaSourceMap = {};
// Replace the global MediaSource with our stub
if (typeof window !== 'undefined') {
window.MediaSource = MediaSourceStub;
window.ManagedMediaSource = MediaSourceStub;
window.SourceBuffer = SourceBufferStub;
window.XMLHttpRequest = XMLHttpRequestStub;
URL.createObjectURL = function(ms) {
const url = "blob:mock-media-source:" + ms.internalId;
window.mediaSourceMap[url] = ms;
return url;
};
URL.revokeObjectURL = function(url) {
};
if (global.isJsCore) {
global.HTMLVideoElement = VideoElementStub;
global.self.MediaSource = window.MediaSource;
global.self.ManagedMediaSource = window.ManagedMediaSource;
global.self.SourceBuffer = window.SourceBuffer;
global.self.XMLHttpRequest = window.XMLHttpRequest;
global.self.HTMLVideoElement = VideoElementStub;
}
}
function postPlayerEvent(id, eventName, eventData) {
if (window.webkit && window.webkit.messageHandlers) {
window.webkit.messageHandlers.performAction.postMessage({'instanceId': id, 'event': eventName, 'data': eventData});
}
}
export class HlsPlayerInstance {
constructor(id) {
this.id = id;
this.isManifestParsed = false;
this.currentTimeUpdateTimeout = null;
this.notifySeekedOnNextStatusUpdate = false;
this.video = new VideoElementStub(this.id);
}
playerInitialize(params) {
this.video.addEventListener("playing", () => {
this.refreshPlayerStatus();
});
this.video.addEventListener("pause", () => {
this.refreshPlayerStatus();
});
this.video.addEventListener("seeking", () => {
this.refreshPlayerStatus();
});
this.video.addEventListener("waiting", () => {
this.refreshPlayerStatus();
});
this.hls = new Hls({
startLevel: 0,
testBandwidth: false,
debug: params['debug'] || true,
autoStartLoad: false,
backBufferLength: 30,
maxBufferLength: 60,
maxMaxBufferLength: 60,
maxFragLookUpTolerance: 0.001,
nudgeMaxRetry: 10000
});
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.isManifestParsed = true;
this.refreshPlayerStatus();
});
this.hls.on(Hls.Events.LEVEL_SWITCHED, () => {
this.refreshPlayerStatus();
});
this.hls.on(Hls.Events.LEVELS_UPDATED, () => {
this.refreshPlayerStatus();
});
this.hls.loadSource(params["urlPrefix"] + "master.m3u8");
this.hls.attachMedia(this.video);
}
playerLoad(initialLevelIndex) {
this.hls.startLevel = initialLevelIndex;
this.hls.startLoad(-1, false);
}
playerPlay() {
this.video.play();
}
playerPause() {
this.video.pause();
}
playerSetBaseRate(value) {
this.video.playbackRate = value;
}
playerSetLevel(level) {
if (level >= 0) {
this.hls.currentLevel = level;
} else {
this.hls.currentLevel = -1;
}
}
playerSetCapAutoLevel(level) {
if (level >= 0) {
this.hls.autoLevelCapping = level;
} else {
this.hls.autoLevelCapping = -1;
//this.hls.currentLevel = -1;
}
}
playerSeek(value) {
this.video.currentTime = value;
}
playerSetIsMuted(value) {
this.video.muted = value;
}
getLevels() {
var levels = [];
for (var i = 0; i < this.hls.levels.length; i++) {
var level = this.hls.levels[i];
levels.push({
'index': i,
'bitrate': level.bitrate || 0,
'width': level.width || 0,
'height': level.height || 0
});
}
return levels;
}
refreshPlayerStatus() {
var isPlaying = false;
if (!this.video.paused && !this.video.ended && this.video.readyState > 2) {
isPlaying = true;
}
postPlayerEvent(this.id, 'playerStatus', {
'isReady': this.isManifestParsed,
'isPlaying': !this.video.paused,
'rate': isPlaying ? this.video.playbackRate : 0.0,
'defaultRate': this.video.playbackRate,
'levels': this.getLevels(),
'currentLevel': this.hls.currentLevel
});
this.refreshPlayerCurrentTime();
if (isPlaying) {
if (this.currentTimeUpdateTimeout == null) {
this.currentTimeUpdateTimeout = setTimeout(() => {
this.refreshPlayerCurrentTime();
}, 200);
}
} else {
if(this.currentTimeUpdateTimeout != null){
clearTimeout(this.currentTimeUpdateTimeout);
this.currentTimeUpdateTimeout = null;
}
}
if (this.notifySeekedOnNextStatusUpdate) {
this.notifySeekedOnNextStatusUpdate = false;
this.video.notifySeeked();
}
}
playerNotifySeekedOnNextStatusUpdate() {
this.notifySeekedOnNextStatusUpdate = true;
}
refreshPlayerCurrentTime() {
postPlayerEvent(this.id, 'playerCurrentTime', {
'value': this.video.currentTime
});
this.currentTimeUpdateTimeout = setTimeout(() => {
this.refreshPlayerCurrentTime()
}, 200);
}
}
window.invokeOnLoad = function() {
postPlayerEvent(this.id, 'windowOnLoad', {
});
}
window.onload = () => {
window.invokeOnLoad();
};
window.hlsPlayer_instances = {};
window.hlsPlayer_makeInstance = function(id) {
window.hlsPlayer_instances[id] = new HlsPlayerInstance(id);
}
window.hlsPlayer_destroyInstance = function(id) {
const instance = window.hlsPlayer_instances[id];
if (instance) {
delete window.hlsPlayer_instances[id];
instance.video.pause();
instance.hls.destroy();
}
}
window.bridgeInvokeCallback = bridgeInvokeCallback;
if (global.isJsCore) {
window.onload();
}
@@ -0,0 +1,15 @@
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
video {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
object-fit: fill;
}
@@ -0,0 +1,35 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Production',
scriptLoading: 'blocking',
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
publicPath: '',
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src/index.js'),
},
{
test: /\.css$/i,
use: [
'style-loader',
'css-loader'
],
},
],
},
};
@@ -0,0 +1,10 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
static: './dist',
},
});
@@ -0,0 +1,15 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
optimization: {
minimize: true,
minimizer: [new TerserPlugin({
terserOptions: {
compress: true,
},
})],
},
});