morethanwords
4 years ago
39 changed files with 918 additions and 944 deletions
File diff suppressed because one or more lines are too long
@ -1,272 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (c) 2018-present, Evgeny Nadymov |
|
||||||
* |
|
||||||
* This source code is licensed under the GPL v.3.0 license found in the |
|
||||||
* LICENSE file in the root directory of this source tree. |
|
||||||
*/ |
|
||||||
|
|
||||||
import MP4Box from 'mp4box/dist/mp4box.all.min'; |
|
||||||
//import { LOG, logSourceBufferRanges } from '../Utils/Common';
|
|
||||||
|
|
||||||
let LOG = () => console.log(...arguments); |
|
||||||
|
|
||||||
export default class MP4Source { |
|
||||||
constructor(video, getBufferAsync) { |
|
||||||
this.mp4file = null; |
|
||||||
this.nextBufferStart = 0; |
|
||||||
this.mediaSource = null; |
|
||||||
this.ready = false; |
|
||||||
this.bufferedTime = 40; |
|
||||||
|
|
||||||
this.beforeMoovBufferSize = 32 * 1024; |
|
||||||
this.moovBufferSize = 512 * 1024; |
|
||||||
this.bufferSize = 1024 * 1024; |
|
||||||
this.seekBufferSize = 1024 * 1024; |
|
||||||
|
|
||||||
this.currentBufferSize = this.beforeMoovBufferSize; |
|
||||||
this.nbSamples = 10; |
|
||||||
this.video = video; |
|
||||||
this.getBufferAsync = getBufferAsync; |
|
||||||
this.expectedSize = this.video.video.expected_size; |
|
||||||
|
|
||||||
this.seeking = false; |
|
||||||
this.loading = false; |
|
||||||
this.url = null; |
|
||||||
|
|
||||||
this.init(video.duration); |
|
||||||
} |
|
||||||
|
|
||||||
init(videoDuration) { |
|
||||||
const mediaSource = new MediaSource(); |
|
||||||
mediaSource.addEventListener('sourceopen', async () => { |
|
||||||
LOG('[MediaSource] sourceopen start', this.mediaSource, this); |
|
||||||
|
|
||||||
if (this.mediaSource.sourceBuffers.length > 0) return; |
|
||||||
|
|
||||||
const mp4File = MP4Box.createFile(); |
|
||||||
mp4File.onMoovStart = () => { |
|
||||||
LOG('[MP4Box] onMoovStart'); |
|
||||||
this.currentBufferSize = this.moovBufferSize; |
|
||||||
}; |
|
||||||
mp4File.onError = error => { |
|
||||||
LOG('[MP4Box] onError', error); |
|
||||||
}; |
|
||||||
mp4File.onReady = info => { |
|
||||||
LOG('[MP4Box] onReady', info); |
|
||||||
this.ready = true; |
|
||||||
this.currentBufferSize = this.bufferSize; |
|
||||||
const { isFragmented, timescale, fragment_duration, duration } = info; |
|
||||||
|
|
||||||
if (!fragment_duration && !duration) { |
|
||||||
this.mediaSource.duration = videoDuration; |
|
||||||
this.bufferedTime = videoDuration; |
|
||||||
} else { |
|
||||||
this.mediaSource.duration = isFragmented |
|
||||||
? fragment_duration / timescale |
|
||||||
: duration / timescale; |
|
||||||
} |
|
||||||
|
|
||||||
for (let i = 0; i < info.tracks.length; i++) { |
|
||||||
this.addSourceBuffer(mp4File, this.mediaSource, info.tracks[i]); |
|
||||||
} |
|
||||||
|
|
||||||
const initSegs = mp4File.initializeSegmentation(); |
|
||||||
LOG('[MP4Box] initializeSegmentation', initSegs); |
|
||||||
|
|
||||||
for (let i = 0; i < initSegs.length; i++) { |
|
||||||
const { user: sourceBuffer } = initSegs[i]; |
|
||||||
sourceBuffer.onupdateend = () => { |
|
||||||
sourceBuffer.initSegs = true; |
|
||||||
sourceBuffer.onupdateend = this.handleSourceBufferUpdateEnd; |
|
||||||
}; |
|
||||||
sourceBuffer.appendBuffer(initSegs[i].buffer); |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[MP4Box] start fragmentation'); |
|
||||||
mp4File.start(); |
|
||||||
}; |
|
||||||
mp4File.onSegment = (id, sourceBuffer, buffer, sampleNum, is_last) => { |
|
||||||
const isLast = (sampleNum + this.nbSamples) > sourceBuffer.nb_samples; |
|
||||||
|
|
||||||
LOG('[MP4Box] onSegment', id, buffer, `${sampleNum}/${sourceBuffer.nb_samples}`, isLast, sourceBuffer.timestampOffset); |
|
||||||
|
|
||||||
if (mediaSource.readyState !== 'open') { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
sourceBuffer.pendingUpdates.push({ id, buffer, sampleNum, isLast }); |
|
||||||
if (sourceBuffer.initSegs && !sourceBuffer.updating) { |
|
||||||
this.handleSourceBufferUpdateEnd({ target: sourceBuffer, mediaSource: this.mediaSource }); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
this.nextBufferStart = 0; |
|
||||||
this.mp4file = mp4File; |
|
||||||
LOG('[MediaSource] sourceopen end', this, this.mp4file); |
|
||||||
|
|
||||||
this.loadNextBuffer(); |
|
||||||
}); |
|
||||||
mediaSource.addEventListener('sourceended', () => { |
|
||||||
LOG('[MediaSource] sourceended', mediaSource.readyState); |
|
||||||
}); |
|
||||||
mediaSource.addEventListener('sourceclose', () => { |
|
||||||
LOG('[MediaSource] sourceclose', mediaSource.readyState); |
|
||||||
}); |
|
||||||
|
|
||||||
this.mediaSource = mediaSource; |
|
||||||
} |
|
||||||
|
|
||||||
addSourceBuffer(file, source, track) { |
|
||||||
if (!track) return null; |
|
||||||
|
|
||||||
const { id, codec, type: trackType, nb_samples } = track; |
|
||||||
const type = `video/mp4; codecs="${codec}"`; |
|
||||||
if (!MediaSource.isTypeSupported(type)) { |
|
||||||
LOG('[addSourceBuffer] not supported', type); |
|
||||||
return null; |
|
||||||
} |
|
||||||
// if (trackType !== 'video') {
|
|
||||||
// LOG('[addSourceBuffer] skip', trackType);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
const sourceBuffer = source.addSourceBuffer(type); |
|
||||||
sourceBuffer.id = id; |
|
||||||
sourceBuffer.pendingUpdates = []; |
|
||||||
sourceBuffer.nb_samples = nb_samples; |
|
||||||
file.setSegmentOptions(id, sourceBuffer, { nbSamples: this.nbSamples }); |
|
||||||
LOG('[addSourceBuffer] add', id, codec, trackType); |
|
||||||
|
|
||||||
return sourceBuffer; |
|
||||||
} |
|
||||||
|
|
||||||
handleSourceBufferUpdateEnd = event => { |
|
||||||
const { target: sourceBuffer } = event; |
|
||||||
const { mediaSource, mp4file } = this; |
|
||||||
|
|
||||||
if (!sourceBuffer) return; |
|
||||||
if (sourceBuffer.updating) return; |
|
||||||
|
|
||||||
//logSourceBufferRanges(sourceBuffer, 0, 0);
|
|
||||||
|
|
||||||
const { pendingUpdates } = sourceBuffer; |
|
||||||
if (!pendingUpdates) return; |
|
||||||
if (!pendingUpdates.length) { |
|
||||||
if (sourceBuffer.isLast && mediaSource.readyState === 'open') { |
|
||||||
LOG('[SourceBuffer] updateend endOfStream start', sourceBuffer.id); |
|
||||||
if (Array.from(mediaSource.sourceBuffers).every(x => !x.pendingUpdates.length && !x.updating)) { |
|
||||||
mediaSource.endOfStream(); |
|
||||||
LOG('[SourceBuffer] updateend endOfStream stop', sourceBuffer.id); |
|
||||||
} |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
const update = pendingUpdates.shift(); |
|
||||||
if (!update) return; |
|
||||||
|
|
||||||
const { id, buffer, sampleNum, isLast } = update; |
|
||||||
|
|
||||||
if (sampleNum) { |
|
||||||
LOG('[SourceBuffer] updateend releaseUsedSamples', id, sampleNum); |
|
||||||
mp4file.releaseUsedSamples(id, sampleNum); |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[SourceBuffer] updateend end', sourceBuffer.id, sourceBuffer.pendingUpdates.length); |
|
||||||
sourceBuffer.isLast = isLast; |
|
||||||
sourceBuffer.appendBuffer(buffer); |
|
||||||
}; |
|
||||||
|
|
||||||
getURL() { |
|
||||||
this.url = this.url || URL.createObjectURL(this.mediaSource); |
|
||||||
|
|
||||||
return this.url; |
|
||||||
} |
|
||||||
|
|
||||||
seek(currentTime, buffered) { |
|
||||||
const seekInfo = this.mp4file.seek(currentTime, true); |
|
||||||
this.nextBufferStart = seekInfo.offset; |
|
||||||
|
|
||||||
let loadNextBuffer = buffered.length === 0; |
|
||||||
for (let i = 0; i < buffered.length; i++) { |
|
||||||
const start = buffered.start(i); |
|
||||||
const end = buffered.end(i); |
|
||||||
|
|
||||||
if (start <= currentTime && currentTime + this.bufferedTime > end) { |
|
||||||
loadNextBuffer = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[player] onSeeked', loadNextBuffer, currentTime, seekInfo, this.nextBufferStart); |
|
||||||
if (loadNextBuffer) { |
|
||||||
this.loadNextBuffer(true); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
timeUpdate(currentTime, duration, buffered) { |
|
||||||
const ranges = []; |
|
||||||
for (let i = 0; i < buffered.length; i++) { |
|
||||||
ranges.push({ start: buffered.start(i), end: buffered.end(i)}) |
|
||||||
} |
|
||||||
|
|
||||||
let loadNextBuffer = buffered.length === 0; |
|
||||||
let hasRange = false; |
|
||||||
for (let i = 0; i < buffered.length; i++) { |
|
||||||
const start = buffered.start(i); |
|
||||||
const end = buffered.end(i); |
|
||||||
|
|
||||||
if (start <= currentTime && currentTime <= end) { |
|
||||||
hasRange = true; |
|
||||||
if (end < duration && currentTime + this.bufferedTime > end) { |
|
||||||
loadNextBuffer = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (!hasRange) { |
|
||||||
loadNextBuffer = true; |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[player] timeUpdate', loadNextBuffer, currentTime, duration, JSON.stringify(ranges)); |
|
||||||
if (loadNextBuffer) { |
|
||||||
this.loadNextBuffer(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async loadNextBuffer(seek = false) { |
|
||||||
const { nextBufferStart, loading, currentBufferSize, mp4file } = this; |
|
||||||
LOG('[player] loadNextBuffer', nextBufferStart === undefined, loading, !mp4file); |
|
||||||
if (!mp4file) return; |
|
||||||
if (nextBufferStart === undefined) return; |
|
||||||
if (loading) return; |
|
||||||
|
|
||||||
this.loading = true; |
|
||||||
let bufferSize = seek ? this.seekBufferSize : this.bufferSize; |
|
||||||
if (nextBufferStart + bufferSize > this.expectedSize) { |
|
||||||
bufferSize = this.expectedSize - nextBufferStart; |
|
||||||
} |
|
||||||
const nextBuffer = await this.getBufferAsync(nextBufferStart, nextBufferStart + bufferSize); |
|
||||||
nextBuffer.fileStart = nextBufferStart; |
|
||||||
|
|
||||||
LOG('[player] loadNextBuffer start', nextBuffer.byteLength, nextBufferStart); |
|
||||||
if (nextBuffer.byteLength) { |
|
||||||
this.nextBufferStart = mp4file.appendBuffer(nextBuffer); |
|
||||||
} else { |
|
||||||
this.nextBufferStart = undefined; |
|
||||||
} |
|
||||||
LOG('[player] loadNextBuffer stop', nextBuffer.byteLength, nextBufferStart, this.nextBufferStart); |
|
||||||
|
|
||||||
if (nextBuffer.byteLength < currentBufferSize) { |
|
||||||
LOG('[player] loadNextBuffer flush'); |
|
||||||
this.mp4file.flush(); |
|
||||||
} |
|
||||||
|
|
||||||
this.loading = false; |
|
||||||
if (!this.ready) { |
|
||||||
LOG('[player] loadNextBuffer next'); |
|
||||||
this.loadNextBuffer(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,279 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (c) 2018-present, Evgeny Nadymov |
|
||||||
* |
|
||||||
* This source code is licensed under the GPL v.3.0 license found in the |
|
||||||
* LICENSE file in the root directory of this source tree. |
|
||||||
*/ |
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import MP4Box from 'mp4box/dist/mp4box.all.min'; |
|
||||||
|
|
||||||
let LOG = (...args: any[]) => { |
|
||||||
console.log(...args); |
|
||||||
}; |
|
||||||
|
|
||||||
export default class MP4Source { |
|
||||||
private mp4file: any; |
|
||||||
private nextBufferStart = 0; |
|
||||||
private mediaSource: MediaSource = null; |
|
||||||
private ready = false; |
|
||||||
private bufferedTime = 40; |
|
||||||
|
|
||||||
private beforeMoovBufferSize = 32 * 1024; |
|
||||||
private moovBufferSize = 512 * 1024; |
|
||||||
private bufferSize = 512 * 1024; |
|
||||||
private seekBufferSize = 512 * 1024; |
|
||||||
|
|
||||||
private currentBufferSize = this.beforeMoovBufferSize; |
|
||||||
private nbSamples = 12; |
|
||||||
private expectedSize: number; |
|
||||||
|
|
||||||
private seeking = false; |
|
||||||
private loading = false; |
|
||||||
private url: string = null; |
|
||||||
|
|
||||||
constructor(private video: {duration: number, video: {expected_size: number}}, private getBufferAsync: (start: number, end: number) => Promise<ArrayBuffer>) { |
|
||||||
this.expectedSize = this.video.video.expected_size; |
|
||||||
|
|
||||||
this.init(video.duration); |
|
||||||
} |
|
||||||
|
|
||||||
init(videoDuration: any) { |
|
||||||
const mediaSource = new MediaSource(); |
|
||||||
mediaSource.addEventListener('sourceopen', async () => { |
|
||||||
LOG('[MediaSource] sourceopen start', this.mediaSource, this); |
|
||||||
|
|
||||||
if (this.mediaSource.sourceBuffers.length > 0) return; |
|
||||||
|
|
||||||
const mp4File = MP4Box.createFile(); |
|
||||||
mp4File.onMoovStart = () => { |
|
||||||
LOG('[MP4Box] onMoovStart'); |
|
||||||
this.currentBufferSize = this.moovBufferSize; |
|
||||||
}; |
|
||||||
mp4File.onError = (error: any) => { |
|
||||||
LOG('[MP4Box] onError', error); |
|
||||||
}; |
|
||||||
mp4File.onReady = (info: any) => { |
|
||||||
LOG('[MP4Box] onReady', info); |
|
||||||
this.ready = true; |
|
||||||
this.currentBufferSize = this.bufferSize; |
|
||||||
const { isFragmented, timescale, fragment_duration, duration } = info; |
|
||||||
|
|
||||||
if (!fragment_duration && !duration) { |
|
||||||
this.mediaSource.duration = videoDuration; |
|
||||||
this.bufferedTime = videoDuration; |
|
||||||
} else { |
|
||||||
this.mediaSource.duration = isFragmented |
|
||||||
? fragment_duration / timescale |
|
||||||
: duration / timescale; |
|
||||||
} |
|
||||||
|
|
||||||
for (let i = 0; i < info.tracks.length; i++) { |
|
||||||
this.addSourceBuffer(mp4File, this.mediaSource, info.tracks[i]); |
|
||||||
} |
|
||||||
|
|
||||||
const initSegs = mp4File.initializeSegmentation(); |
|
||||||
LOG('[MP4Box] initializeSegmentation', initSegs); |
|
||||||
|
|
||||||
for (let i = 0; i < initSegs.length; i++) { |
|
||||||
const { user: sourceBuffer } = initSegs[i]; |
|
||||||
sourceBuffer.onupdateend = () => { |
|
||||||
sourceBuffer.initSegs = true; |
|
||||||
sourceBuffer.onupdateend = this.handleSourceBufferUpdateEnd; |
|
||||||
}; |
|
||||||
sourceBuffer.appendBuffer(initSegs[i].buffer); |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[MP4Box] start fragmentation'); |
|
||||||
mp4File.start(); |
|
||||||
|
|
||||||
setInterval(() => { |
|
||||||
this.loadNextBuffer(); |
|
||||||
}, 1e3); |
|
||||||
}; |
|
||||||
mp4File.onSegment = (id: any, sourceBuffer: any, buffer: any, sampleNum: any, is_last: boolean) => { |
|
||||||
const isLast = (sampleNum + this.nbSamples) > sourceBuffer.nb_samples; |
|
||||||
|
|
||||||
LOG('[MP4Box] onSegment', id, buffer, `${sampleNum}/${sourceBuffer.nb_samples}`, isLast, sourceBuffer.timestampOffset); |
|
||||||
|
|
||||||
if (mediaSource.readyState !== 'open') { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
sourceBuffer.pendingUpdates.push({ id, buffer, sampleNum, isLast }); |
|
||||||
if (sourceBuffer.initSegs && !sourceBuffer.updating) { |
|
||||||
this.handleSourceBufferUpdateEnd({ target: sourceBuffer, mediaSource: this.mediaSource }); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
this.nextBufferStart = 0; |
|
||||||
this.mp4file = mp4File; |
|
||||||
LOG('[MediaSource] sourceopen end', this, this.mp4file); |
|
||||||
|
|
||||||
this.loadNextBuffer(); |
|
||||||
}); |
|
||||||
mediaSource.addEventListener('sourceended', () => { |
|
||||||
LOG('[MediaSource] sourceended', mediaSource.readyState); |
|
||||||
}); |
|
||||||
mediaSource.addEventListener('sourceclose', () => { |
|
||||||
LOG('[MediaSource] sourceclose', mediaSource.readyState); |
|
||||||
}); |
|
||||||
|
|
||||||
this.mediaSource = mediaSource; |
|
||||||
} |
|
||||||
|
|
||||||
addSourceBuffer(file: any, source: any, track: any) { |
|
||||||
if (!track) return null; |
|
||||||
|
|
||||||
const { id, codec, type: trackType, nb_samples } = track; |
|
||||||
const type = `video/mp4; codecs="${codec}"`; |
|
||||||
if (!MediaSource.isTypeSupported(type)) { |
|
||||||
LOG('[addSourceBuffer] not supported', type); |
|
||||||
return null; |
|
||||||
} |
|
||||||
// if (trackType !== 'video') {
|
|
||||||
// LOG('[addSourceBuffer] skip', trackType);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
const sourceBuffer = source.addSourceBuffer(type); |
|
||||||
sourceBuffer.id = id; |
|
||||||
sourceBuffer.pendingUpdates = []; |
|
||||||
sourceBuffer.nb_samples = nb_samples; |
|
||||||
file.setSegmentOptions(id, sourceBuffer, { nbSamples: this.nbSamples }); |
|
||||||
LOG('[addSourceBuffer] add', id, codec, trackType); |
|
||||||
|
|
||||||
return sourceBuffer; |
|
||||||
} |
|
||||||
|
|
||||||
handleSourceBufferUpdateEnd = (event: any) => { |
|
||||||
const { target: sourceBuffer } = event; |
|
||||||
const { mediaSource, mp4file } = this; |
|
||||||
|
|
||||||
if (!sourceBuffer) return; |
|
||||||
if (sourceBuffer.updating) return; |
|
||||||
|
|
||||||
//logSourceBufferRanges(sourceBuffer, 0, 0);
|
|
||||||
|
|
||||||
const { pendingUpdates } = sourceBuffer; |
|
||||||
if (!pendingUpdates) return; |
|
||||||
if (!pendingUpdates.length) { |
|
||||||
if (sourceBuffer.isLast && mediaSource.readyState === 'open') { |
|
||||||
LOG('[SourceBuffer] updateend endOfStream start', sourceBuffer.id); |
|
||||||
if (Array.from(mediaSource.sourceBuffers).every((x: any) => !x.pendingUpdates.length && !x.updating)) { |
|
||||||
mediaSource.endOfStream(); |
|
||||||
LOG('[SourceBuffer] updateend endOfStream stop', sourceBuffer.id); |
|
||||||
} |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
const update = pendingUpdates.shift(); |
|
||||||
if (!update) return; |
|
||||||
|
|
||||||
const { id, buffer, sampleNum, isLast } = update; |
|
||||||
|
|
||||||
if (sampleNum) { |
|
||||||
LOG('[SourceBuffer] updateend releaseUsedSamples', id, sampleNum); |
|
||||||
mp4file.releaseUsedSamples(id, sampleNum); |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[SourceBuffer] updateend end', sourceBuffer.id, sourceBuffer.pendingUpdates.length); |
|
||||||
sourceBuffer.isLast = isLast; |
|
||||||
sourceBuffer.appendBuffer(buffer); |
|
||||||
}; |
|
||||||
|
|
||||||
getURL() { |
|
||||||
this.url = this.url || URL.createObjectURL(this.mediaSource); |
|
||||||
|
|
||||||
return this.url; |
|
||||||
} |
|
||||||
|
|
||||||
seek(currentTime: number, buffered: any) { |
|
||||||
const seekInfo = this.mp4file.seek(currentTime, true); |
|
||||||
this.nextBufferStart = seekInfo.offset; |
|
||||||
|
|
||||||
let loadNextBuffer = buffered.length === 0; |
|
||||||
for (let i = 0; i < buffered.length; i++) { |
|
||||||
const start = buffered.start(i); |
|
||||||
const end = buffered.end(i); |
|
||||||
|
|
||||||
if (start <= currentTime && currentTime + this.bufferedTime > end) { |
|
||||||
loadNextBuffer = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[player] onSeeked', loadNextBuffer, currentTime, seekInfo, this.nextBufferStart); |
|
||||||
if (loadNextBuffer) { |
|
||||||
this.loadNextBuffer(true); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
timeUpdate(currentTime: number, duration: number, buffered: any) { |
|
||||||
const ranges = []; |
|
||||||
for (let i = 0; i < buffered.length; i++) { |
|
||||||
ranges.push({ start: buffered.start(i), end: buffered.end(i)}) |
|
||||||
} |
|
||||||
|
|
||||||
let loadNextBuffer = buffered.length === 0; |
|
||||||
let hasRange = false; |
|
||||||
for (let i = 0; i < buffered.length; i++) { |
|
||||||
const start = buffered.start(i); |
|
||||||
const end = buffered.end(i); |
|
||||||
|
|
||||||
if (start <= currentTime && currentTime <= end) { |
|
||||||
hasRange = true; |
|
||||||
if (end < duration && currentTime + this.bufferedTime > end) { |
|
||||||
loadNextBuffer = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (!hasRange) { |
|
||||||
loadNextBuffer = true; |
|
||||||
} |
|
||||||
|
|
||||||
LOG('[player] timeUpdate', loadNextBuffer, currentTime, duration, JSON.stringify(ranges)); |
|
||||||
if (loadNextBuffer) { |
|
||||||
this.loadNextBuffer(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async loadNextBuffer(seek = false) { |
|
||||||
const { nextBufferStart, loading, currentBufferSize, mp4file } = this; |
|
||||||
LOG('[player] loadNextBuffer', nextBufferStart === undefined, loading, !mp4file); |
|
||||||
if (!mp4file) return; |
|
||||||
if (nextBufferStart === undefined) return; |
|
||||||
if (loading) return; |
|
||||||
|
|
||||||
this.loading = true; |
|
||||||
let bufferSize = seek ? this.seekBufferSize : this.bufferSize; |
|
||||||
if (nextBufferStart + bufferSize > this.expectedSize) { |
|
||||||
bufferSize = this.expectedSize - nextBufferStart; |
|
||||||
} |
|
||||||
const nextBuffer = await this.getBufferAsync(nextBufferStart, nextBufferStart + bufferSize); |
|
||||||
// @ts-ignore
|
|
||||||
nextBuffer.fileStart = nextBufferStart; |
|
||||||
|
|
||||||
LOG('[player] loadNextBuffer start', nextBuffer.byteLength, nextBufferStart); |
|
||||||
if (nextBuffer.byteLength) { |
|
||||||
this.nextBufferStart = mp4file.appendBuffer(nextBuffer); |
|
||||||
} else { |
|
||||||
this.nextBufferStart = undefined; |
|
||||||
} |
|
||||||
LOG('[player] loadNextBuffer stop', nextBuffer.byteLength, nextBufferStart, this.nextBufferStart); |
|
||||||
|
|
||||||
if (nextBuffer.byteLength < currentBufferSize) { |
|
||||||
LOG('[player] loadNextBuffer flush'); |
|
||||||
this.mp4file.flush(); |
|
||||||
} |
|
||||||
|
|
||||||
this.loading = false; |
|
||||||
if (!this.ready) { |
|
||||||
LOG('[player] loadNextBuffer next'); |
|
||||||
this.loadNextBuffer(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,195 @@ |
|||||||
|
import AppStorage from '../storage'; |
||||||
|
import appMessagesManager, { Dialog, DialogsStorage, FiltersStorage } from './appMessagesManager'; |
||||||
|
import appMessagesIDsManager from './appMessagesIDsManager'; |
||||||
|
import appPeersManager from './appPeersManager'; |
||||||
|
import appChatsManager from './appChatsManager'; |
||||||
|
import appUsersManager from './appUsersManager'; |
||||||
|
import apiUpdatesManager from './apiUpdatesManager'; |
||||||
|
import { copy } from '../utils'; |
||||||
|
import { logger } from '../polyfill'; |
||||||
|
|
||||||
|
export class AppStateManager { |
||||||
|
public loaded: Promise<any>; |
||||||
|
private log = logger('STATE'/* , LogLevels.error */); |
||||||
|
|
||||||
|
private state: any = {}; |
||||||
|
private peers: {[peerID: number]: any} = {}; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.loadSavedState(); |
||||||
|
} |
||||||
|
|
||||||
|
public loadSavedState() { |
||||||
|
if(this.loaded) return this.loaded; |
||||||
|
return this.loaded = new Promise((resolve, reject) => { |
||||||
|
AppStorage.get<{ |
||||||
|
dialogs: Dialog[], |
||||||
|
allDialogsLoaded: DialogsStorage['allDialogsLoaded'], |
||||||
|
peers: any[], |
||||||
|
messages: any[], |
||||||
|
contactsList: number[], |
||||||
|
updates: any, |
||||||
|
filters: FiltersStorage['filters'], |
||||||
|
maxSeenMsgID: number |
||||||
|
}>('state').then((state) => { |
||||||
|
const {dialogs, allDialogsLoaded, peers, messages, contactsList, maxSeenMsgID, updates, filters} = state; |
||||||
|
this.state = state ?? {}; |
||||||
|
this.log('state res', dialogs, messages); |
||||||
|
|
||||||
|
if(maxSeenMsgID && !appMessagesIDsManager.getMessageIDInfo(maxSeenMsgID)[1]) { |
||||||
|
appMessagesManager.maxSeenID = maxSeenMsgID; |
||||||
|
} |
||||||
|
|
||||||
|
//return resolve();
|
||||||
|
|
||||||
|
if(peers) { |
||||||
|
for(let peerID in peers) { |
||||||
|
let peer = peers[peerID]; |
||||||
|
if(+peerID < 0) appChatsManager.saveApiChat(peer); |
||||||
|
else appUsersManager.saveApiUser(peer); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(contactsList && Array.isArray(contactsList) && contactsList.length) { |
||||||
|
contactsList.forEach(userID => { |
||||||
|
appUsersManager.pushContact(userID); |
||||||
|
}); |
||||||
|
appUsersManager.contactsFillPromise = Promise.resolve(appUsersManager.contactsList); |
||||||
|
} |
||||||
|
|
||||||
|
if(messages) { |
||||||
|
/* let tempID = this.tempID; |
||||||
|
|
||||||
|
for(let message of messages) { |
||||||
|
if(message.id < tempID) { |
||||||
|
tempID = message.id; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(tempID != this.tempID) { |
||||||
|
this.log('Set tempID to:', tempID); |
||||||
|
this.tempID = tempID; |
||||||
|
} */ |
||||||
|
|
||||||
|
appMessagesManager.saveMessages(messages); |
||||||
|
|
||||||
|
// FIX FILE_REFERENCE_EXPIRED KOSTIL'1999
|
||||||
|
for(let message of messages) { |
||||||
|
if(message.media) { |
||||||
|
appMessagesManager.wrapSingleMessage(message.mid, true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(allDialogsLoaded) { |
||||||
|
appMessagesManager.dialogsStorage.allDialogsLoaded = allDialogsLoaded; |
||||||
|
} |
||||||
|
|
||||||
|
if(filters) { |
||||||
|
for(const filterID in filters) { |
||||||
|
appMessagesManager.filtersStorage.saveDialogFilter(filters[filterID], false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(dialogs) { |
||||||
|
dialogs.forEachReverse(dialog => { |
||||||
|
appMessagesManager.saveConversation(dialog); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
apiUpdatesManager.attach(updates ?? null); |
||||||
|
|
||||||
|
resolve(state); |
||||||
|
}).catch(resolve).finally(() => { |
||||||
|
setInterval(() => this.saveState(), 10000); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public getState() { |
||||||
|
return this.loadSavedState(); |
||||||
|
} |
||||||
|
|
||||||
|
public saveState() { |
||||||
|
const messages: any[] = []; |
||||||
|
const dialogs: Dialog[] = []; |
||||||
|
const peers: {[peerID: number]: any} = this.peers; |
||||||
|
|
||||||
|
for(const folderID in appMessagesManager.dialogsStorage.byFolders) { |
||||||
|
const folder = appMessagesManager.dialogsStorage.getFolder(+folderID); |
||||||
|
|
||||||
|
for(let dialog of folder) { |
||||||
|
const historyStorage = appMessagesManager.historiesStorage[dialog.peerID]; |
||||||
|
const history = [].concat(historyStorage?.pending ?? [], historyStorage?.history ?? []); |
||||||
|
|
||||||
|
dialog = copy(dialog); |
||||||
|
let removeUnread = 0; |
||||||
|
for(const mid of history) { |
||||||
|
const message = appMessagesManager.getMessage(mid); |
||||||
|
if(/* message._ != 'messageEmpty' && */message.id > 0) { |
||||||
|
messages.push(message); |
||||||
|
|
||||||
|
if(message.fromID != dialog.peerID) { |
||||||
|
peers[message.fromID] = appPeersManager.getPeer(message.fromID); |
||||||
|
} |
||||||
|
|
||||||
|
dialog.top_message = message.mid; |
||||||
|
|
||||||
|
break; |
||||||
|
} else if(message.pFlags && message.pFlags.unread) { |
||||||
|
++removeUnread; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(removeUnread && dialog.unread_count) dialog.unread_count -= removeUnread; |
||||||
|
|
||||||
|
dialogs.push(dialog); |
||||||
|
|
||||||
|
peers[dialog.peerID] = appPeersManager.getPeer(dialog.peerID); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const us = apiUpdatesManager.updatesState; |
||||||
|
const updates = { |
||||||
|
seq: us.seq, |
||||||
|
pts: us.pts, |
||||||
|
date: us.date |
||||||
|
}; |
||||||
|
|
||||||
|
const contactsList = [...appUsersManager.contactsList]; |
||||||
|
for(const userID of contactsList) { |
||||||
|
if(!peers[userID]) { |
||||||
|
peers[userID] = appUsersManager.getUser(userID); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const filters = appMessagesManager.filtersStorage.filters; |
||||||
|
//const pinnedOrders = appMessagesManager.dialogsStorage.pinnedOrders;
|
||||||
|
|
||||||
|
AppStorage.set({ |
||||||
|
state: Object.assign({}, this.state, { |
||||||
|
dialogs, |
||||||
|
messages, |
||||||
|
allDialogsLoaded: appMessagesManager.dialogsStorage.allDialogsLoaded, |
||||||
|
peers, |
||||||
|
contactsList, |
||||||
|
filters, |
||||||
|
//pinnedOrders,
|
||||||
|
updates, |
||||||
|
maxSeenMsgID: appMessagesManager.maxSeenID |
||||||
|
}) |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public pushToState(key: string, value: any) { |
||||||
|
this.state[key] = value; |
||||||
|
} |
||||||
|
|
||||||
|
public pushPeer(peerID: number) { |
||||||
|
this.peers[peerID] = appPeersManager.getPeer(peerID); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const appStateManager = new AppStateManager(); |
||||||
|
export default appStateManager; |
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue