-
Notifications
You must be signed in to change notification settings - Fork 2
/
RtspStreamer.ts
117 lines (99 loc) · 3.41 KB
/
RtspStreamer.ts
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
import { Html5VideoPipeline, isRtcpBye } from '@clockworkdog/media-stream-library-browser';
import { COGS_SERVER_PORT } from './helpers/urls';
const DEFAULT_VIDEO_PLAYBACK_RATE = 1;
// Use a faster-than-realtime playback rate by default
// so that it keeps up with a realtime stream in case of video buffering
export const LIVE_VIDEO_PLAYBACK_RATE = 1.1;
/**
* Manages a websocket connection to the COGS TCP relay which can be used to send RTSP video
* feeds to the web.
*/
export default class RtspStreamer {
private _websocketUri: string;
constructor({
hostname = document.location.hostname,
port = COGS_SERVER_PORT,
path = '/tcp-proxy',
}: { hostname?: string; port?: number; path?: string } = {}) {
this._websocketUri = `ws://${hostname}:${port}${path}`;
}
/**
* Start an RTSP video stream on with the given URI on the given video element.
* @returns An object with a function to close the pipeline
*/
public play(params: { uri: string; videoElement: HTMLVideoElement; playbackRate?: number; restartIfStopped?: boolean }): { close: () => void } {
const { uri, videoElement } = params;
videoElement.playsInline = true; // Required for iOS
let pipeline: Html5VideoPipeline;
const startPipeline = () => {
pipeline?.close();
pipeline = new Html5VideoPipeline({
ws: { uri: this._websocketUri },
rtsp: { uri: uri },
mediaElement: videoElement,
});
// Restart stream on RTCP BYE (stream ended)
pipeline.rtsp.onRtcp = (rtcp) => {
if (isRtcpBye(rtcp)) {
console.log('Video stream ended. Restarting.');
videoElement.pause();
setTimeout(startPipeline, 0);
}
};
// Start playback when ready
pipeline.ready.then(() => {
pipeline.rtsp.play();
});
};
startPipeline();
if (params.playbackRate) {
const playbackRate = params.playbackRate ?? DEFAULT_VIDEO_PLAYBACK_RATE;
videoElement.playbackRate = playbackRate;
videoElement.addEventListener('play', () => {
videoElement.playbackRate = playbackRate;
});
}
let removeRestartListeners: (() => void) | null = null;
if (params.restartIfStopped) {
let playing = false;
let interval: NodeJS.Timer | null = null;
const handleTimeUpdate = () => {
playing = true;
};
const handlePlay = () => {
playing = true;
videoElement.addEventListener('timeupdate', handleTimeUpdate);
if (!interval) {
interval = setInterval(() => {
if (!playing) {
console.log('Video stopped playing. Restarting.');
videoElement.pause();
setTimeout(startPipeline, 0);
}
playing = false;
}, 2000);
}
};
const handlePause = () => {
videoElement.removeEventListener('timeupdate', handleTimeUpdate);
if (interval) {
clearInterval(interval);
interval = null;
}
};
videoElement.addEventListener('play', handlePlay);
videoElement.addEventListener('pause', handlePause);
removeRestartListeners = () => {
handlePause();
videoElement.removeEventListener('play', handlePlay);
videoElement.removeEventListener('pause', handlePause);
};
}
return {
close: () => {
pipeline?.close();
removeRestartListeners?.();
},
};
}
}