/
base-stream-controller.js
131 lines (116 loc) · 4.59 KB
/
base-stream-controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import TaskLoop from '../task-loop';
import { FragmentState } from './fragment-tracker';
import { BufferHelper } from '../utils/buffer-helper';
import { logger } from '../utils/logger';
export const State = {
STOPPED: 'STOPPED',
STARTING: 'STARTING',
IDLE: 'IDLE',
PAUSED: 'PAUSED',
KEY_LOADING: 'KEY_LOADING',
FRAG_LOADING: 'FRAG_LOADING',
FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
WAITING_TRACK: 'WAITING_TRACK',
PARSING: 'PARSING',
PARSED: 'PARSED',
BUFFER_FLUSHING: 'BUFFER_FLUSHING',
ENDED: 'ENDED',
ERROR: 'ERROR',
WAITING_INIT_PTS: 'WAITING_INIT_PTS',
WAITING_LEVEL: 'WAITING_LEVEL'
};
export default class BaseStreamController extends TaskLoop {
doTick () {}
startLoad () {}
stopLoad () {
let frag = this.fragCurrent;
if (frag) {
if (frag.loader) {
frag.loader.abort();
}
this.fragmentTracker.removeFragment(frag);
}
if (this.demuxer) {
this.demuxer.destroy();
this.demuxer = null;
}
this.fragCurrent = null;
this.fragPrevious = null;
this.clearInterval();
this.clearNextTick();
this.state = State.STOPPED;
}
_streamEnded (bufferInfo, levelDetails) {
const { fragCurrent, fragmentTracker } = this;
// we just got done loading the final fragment and there is no other buffered range after ...
// rationale is that in case there are any buffered ranges after, it means that there are unbuffered portion in between
// so we should not switch to ENDED in that case, to be able to buffer them
// dont switch to ENDED if we need to backtrack last fragment
if (!levelDetails.live && fragCurrent && !fragCurrent.backtracked && fragCurrent.sn === levelDetails.endSN && !bufferInfo.nextStart) {
const fragState = fragmentTracker.getState(fragCurrent);
return fragState === FragmentState.PARTIAL || fragState === FragmentState.OK;
}
return false;
}
onMediaSeeking () {
const { config, media, mediaBuffer, state } = this;
const currentTime = media ? media.currentTime : null;
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer || media, currentTime, this.config.maxBufferHole);
logger.log(`media seeking to ${Number.isFinite(currentTime) ? currentTime.toFixed(3) : currentTime}`);
if (state === State.FRAG_LOADING) {
let fragCurrent = this.fragCurrent;
// check if we are seeking to a unbuffered area AND if frag loading is in progress
if (bufferInfo.len === 0 && fragCurrent) {
const tolerance = config.maxFragLookUpTolerance;
const fragStartOffset = fragCurrent.start - tolerance;
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
// check if we seek position will be out of currently loaded frag range : if out cancel frag load, if in, don't do anything
if (currentTime < fragStartOffset || currentTime > fragEndOffset) {
if (fragCurrent.loader) {
logger.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
fragCurrent.loader.abort();
}
this.fragCurrent = null;
this.fragPrevious = null;
// switch to IDLE state to load new fragment
this.state = State.IDLE;
} else {
logger.log('seeking outside of buffer but within currently loaded fragment range');
}
}
} else if (state === State.ENDED) {
// if seeking to unbuffered area, clean up fragPrevious
if (bufferInfo.len === 0) {
this.fragPrevious = null;
this.fragCurrent = null;
}
// switch to IDLE state to check for potential new fragment
this.state = State.IDLE;
}
if (media) {
this.lastCurrentTime = currentTime;
}
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
if (!this.loadedmetadata) {
this.nextLoadPosition = this.startPosition = currentTime;
}
// tick to speed up processing
this.tick();
}
onMediaEnded () {
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
this.startPosition = this.lastCurrentTime = 0;
}
onHandlerDestroying () {
this.stopLoad();
super.onHandlerDestroying();
}
onHandlerDestroyed () {
this.state = State.STOPPED;
this.fragmentTracker = null;
}
computeLivePosition (sliding, levelDetails) {
let targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * levelDetails.targetduration;
return sliding + Math.max(0, levelDetails.totalduration - targetLatency);
}
}