mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
26f765c269
@ -332,7 +332,7 @@ private final class ChunkMediaPlayerContext {
|
||||
audioRendererContext.start()
|
||||
self.tick()
|
||||
|
||||
let tickTimer = SwiftSignalKit.Timer(timeout: 1.0 / 60.0, repeat: true, completion: { [weak self] in
|
||||
let tickTimer = SwiftSignalKit.Timer(timeout: 1.0 / 25.0, repeat: true, completion: { [weak self] in
|
||||
self?.tick()
|
||||
}, queue: self.queue)
|
||||
self.tickTimer = tickTimer
|
||||
@ -608,15 +608,31 @@ private final class ChunkMediaPlayerContext {
|
||||
}
|
||||
}
|
||||
|
||||
/*if validParts.isEmpty, let initialSeekTimestamp = self.initialSeekTimestamp {
|
||||
if validParts.isEmpty, let initialSeekTimestamp = self.initialSeekTimestamp {
|
||||
for part in self.partsState.parts {
|
||||
if initialSeekTimestamp >= part.startTime - 0.2 && initialSeekTimestamp < part.endTime {
|
||||
self.initialSeekTimestamp = nil
|
||||
self.seek(timestamp: part.startTime + 0.05)
|
||||
|
||||
self.videoRenderer.flush()
|
||||
|
||||
if let audioRenderer = self.audioRenderer {
|
||||
self.isSeeking = true
|
||||
let queue = self.queue
|
||||
audioRenderer.renderer.flushBuffers(at: CMTime(seconds: part.startTime + 0.1, preferredTimescale: 44100), completion: { [weak self] in
|
||||
queue.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isSeeking = false
|
||||
self.tick()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
self.loadedState.partStates.removeAll(where: { partState in
|
||||
if !validParts.contains(where: { $0.id == partState.part.id }) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,9 +1 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Developement</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
|
||||
<body>
|
||||
<script src="runtime.bundle.js"></script><script src="index.bundle.js"></script></body>
|
||||
</html>
|
||||
<!doctype html><html><head><meta charset="utf-8"><title>Production</title><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><script src="runtime.bundle.js"></script><script src="index.bundle.js"></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
28
submodules/TelegramUniversalVideoContent/PlayerSource/.gitignore
vendored
Normal file
28
submodules/TelegramUniversalVideoContent/PlayerSource/.gitignore
vendored
Normal file
@ -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
|
||||
8
submodules/TelegramUniversalVideoContent/PlayerSource/.vscode/launch.json
vendored
Normal file
8
submodules/TelegramUniversalVideoContent/PlayerSource/.vscode/launch.json
vendored
Normal file
@ -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,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
rm -rf ../HlsBundle
|
||||
mkdir ../HlsBundle
|
||||
npm run build-$1
|
||||
cp ./dist/* ../HlsBundle/
|
||||
4461
submodules/TelegramUniversalVideoContent/PlayerSource/package-lock.json
generated
Normal file
4461
submodules/TelegramUniversalVideoContent/PlayerSource/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
||||
{
|
||||
"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": {
|
||||
"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,210 @@
|
||||
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", {
|
||||
"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.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.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", {
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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);
|
||||
return sourceBuffer;
|
||||
}
|
||||
|
||||
removeSourceBuffer(sourceBuffer) {
|
||||
if (!this.sourceBuffers._remove(sourceBuffer)) {
|
||||
throw new DOMException('SourceBuffer not found', 'NotFoundError');
|
||||
}
|
||||
this.activeSourceBuffers._remove(sourceBuffer);
|
||||
}
|
||||
|
||||
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,173 @@
|
||||
import { TimeRangesStub } from "./TimeRangesStub.js"
|
||||
import { TextTrackStub, TextTrackListStub } from "./TextTrackStub.js"
|
||||
|
||||
export class VideoElementStub extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
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;
|
||||
|
||||
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "constructor", {
|
||||
});
|
||||
|
||||
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", {
|
||||
"currentTime": value
|
||||
}).then((result) => {
|
||||
this.dispatchEvent(new Event('seeked'));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bridgeUpdateBuffered(value) {
|
||||
const updatedRanges = value;
|
||||
var ranges = [];
|
||||
for (var i = 0; i < updatedRanges.length; i += 2) {
|
||||
ranges.push({
|
||||
start: updatedRanges[i],
|
||||
end: updatedRanges[i + 1]
|
||||
});
|
||||
}
|
||||
this.buffered._ranges = 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", {
|
||||
}).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", {
|
||||
}).then((result) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
canPlayType(type) {
|
||||
return 'probably';
|
||||
}
|
||||
|
||||
_getMedia() {
|
||||
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) {
|
||||
const textTrack = new TextTrackStub(kind, label, language);
|
||||
this.textTracks._add(textTrack);
|
||||
return textTrack;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
import Hls from "hls.js";
|
||||
import { VideoElementStub } from "./VideoElementStub.js"
|
||||
import { MediaSourceStub, SourceBufferStub } from "./MediaSourceStub.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);
|
||||
}
|
||||
}
|
||||
|
||||
var useStubs = true;
|
||||
|
||||
window.nextInternalId = 0;
|
||||
window.mediaSourceMap = {};
|
||||
|
||||
// Replace the global MediaSource with our stub
|
||||
if (useStubs && typeof window !== 'undefined') {
|
||||
window.MediaSource = MediaSourceStub;
|
||||
window.ManagedMediaSource = MediaSourceStub;
|
||||
window.SourceBuffer = SourceBufferStub;
|
||||
URL.createObjectURL = function(ms) {
|
||||
const url = "blob:mock-media-source:" + ms.internalId;
|
||||
window.mediaSourceMap[url] = ms;
|
||||
return url;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function postPlayerEvent(eventName, eventData) {
|
||||
if (window.webkit && window.webkit.messageHandlers) {
|
||||
window.webkit.messageHandlers.performAction.postMessage({'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();
|
||||
}
|
||||
});
|
||||
video.addEventListener("playing", function() {
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
video.addEventListener("pause", function() {
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
video.addEventListener("seeking", function() {
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
video.addEventListener("waiting", function() {
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
|
||||
hls = new Hls({
|
||||
startLevel: 0,
|
||||
testBandwidth: false,
|
||||
debug: params['debug'] || true,
|
||||
autoStartLoad: false,
|
||||
backBufferLength: 30,
|
||||
maxBufferLength: 60,
|
||||
maxMaxBufferLength: 60,
|
||||
maxFragLookUpTolerance: 0.001,
|
||||
nudgeMaxRetry: 10000
|
||||
});
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, function() {
|
||||
isManifestParsed = true;
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
|
||||
hls.on(Hls.Events.LEVEL_SWITCHED, function() {
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
hls.on(Hls.Events.LEVELS_UPDATED, function() {
|
||||
refreshPlayerStatus();
|
||||
});
|
||||
|
||||
hls.loadSource('master.m3u8');
|
||||
hls.attachMedia(video);
|
||||
}
|
||||
|
||||
export function playerLoad(initialLevelIndex) {
|
||||
hls.startLevel = initialLevelIndex;
|
||||
hls.startLoad(-1, false);
|
||||
}
|
||||
|
||||
export function playerPlay() {
|
||||
video.play();
|
||||
}
|
||||
|
||||
export function playerPause() {
|
||||
video.pause();
|
||||
}
|
||||
|
||||
export function playerSetBaseRate(value) {
|
||||
video.playbackRate = value;
|
||||
}
|
||||
|
||||
export function playerSetLevel(level) {
|
||||
if (level >= 0) {
|
||||
hls.currentLevel = level;
|
||||
} else {
|
||||
hls.currentLevel = -1;
|
||||
}
|
||||
}
|
||||
|
||||
export function playerSeek(value) {
|
||||
video.currentTime = value;
|
||||
}
|
||||
|
||||
export function playerSetIsMuted(value) {
|
||||
video.muted = value;
|
||||
}
|
||||
|
||||
function getLevels() {
|
||||
var levels = [];
|
||||
for (var i = 0; i < hls.levels.length; i++) {
|
||||
var level = hls.levels[i];
|
||||
levels.push({
|
||||
'index': i,
|
||||
'bitrate': level.bitrate || 0,
|
||||
'width': level.width || 0,
|
||||
'height': level.height || 0
|
||||
});
|
||||
}
|
||||
return levels;
|
||||
}
|
||||
|
||||
function refreshPlayerStatus() {
|
||||
var isPlaying = false;
|
||||
if (!video.paused && !video.ended && video.readyState > 2) {
|
||||
isPlaying = true;
|
||||
}
|
||||
|
||||
postPlayerEvent('playerStatus', {
|
||||
'isReady': isManifestParsed,
|
||||
'isFirstFrameReady': isFirstFrameReady,
|
||||
'isPlaying': !video.paused,
|
||||
'rate': isPlaying ? video.playbackRate : 0.0,
|
||||
'defaultRate': video.playbackRate,
|
||||
'levels': getLevels(),
|
||||
'currentLevel': hls.currentLevel
|
||||
});
|
||||
|
||||
refreshPlayerCurrentTime();
|
||||
|
||||
if (isPlaying) {
|
||||
if (currentTimeUpdateTimeout == null) {
|
||||
currentTimeUpdateTimeout = setTimeout(() => {
|
||||
refreshPlayerCurrentTime();
|
||||
}, 200);
|
||||
}
|
||||
} else {
|
||||
if(currentTimeUpdateTimeout != null){
|
||||
clearTimeout(currentTimeUpdateTimeout);
|
||||
currentTimeUpdateTimeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshPlayerCurrentTime() {
|
||||
postPlayerEvent('playerCurrentTime', {
|
||||
'value': video.currentTime
|
||||
});
|
||||
currentTimeUpdateTimeout = setTimeout(() => {
|
||||
refreshPlayerCurrentTime()
|
||||
}, 200);
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
if (useStubs) {
|
||||
video = new VideoElementStub();
|
||||
} else {
|
||||
video = document.createElement('video');
|
||||
video.playsInline = true;
|
||||
video.controls = true;
|
||||
document.body.appendChild(video);
|
||||
}
|
||||
|
||||
postPlayerEvent('windowOnLoad', {
|
||||
});
|
||||
};
|
||||
|
||||
window.playerInitialize = playerInitialize;
|
||||
window.playerLoad = playerLoad;
|
||||
window.playerPlay = playerPlay;
|
||||
window.playerPause = playerPause;
|
||||
window.playerSetBaseRate = playerSetBaseRate;
|
||||
window.playerSetLevel = playerSetLevel;
|
||||
window.playerSeek = playerSeek;
|
||||
window.playerSetIsMuted = playerSetIsMuted;
|
||||
window.bridgeInvokeCallback = bridgeInvokeCallback;
|
||||
@ -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: 'source-map',
|
||||
devServer: {
|
||||
static: './dist',
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
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,
|
||||
},
|
||||
})],
|
||||
runtimeChunk: 'single',
|
||||
},
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user