-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscreenshareVideoPC.html
297 lines (252 loc) · 10.3 KB
/
screenshareVideoPC.html
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Screenshare a video file</title>
<link rel="stylesheet" href="../style.css">
<style>
div.output {
display: flex;
}
video{
width: 640px;
height: 360px;
background-color: black;
margin: 5px;
}
div#notes{
max-width: 1200px;
}
</style>
</head>
<body>
<div id="notes">
<p >
Choose a video file. After you select your file, a pop window with a video player will load.
Click the Capture to use getDisplayMedia to capture the player and send it over a peer connection.
The output of the peerConnection is shown on the RemoteReceiver window.
</p>
<p>
<code>suppressLocalAudio</code> is used to turn off the audio on the player tab. The user can control the
Screenshare Preview to mute the audio locally or lower the volume (duck) to 10%.
</p>
<p>
To make sure local audio is acting independent of the remote audio, select a different audio output device on the
remote.
</p>
</div>
<div>
<p>
<span>1. </span>
<button id="bbb">Play Video</button>
<label for="videoFile">or load your own video file and open the player</label>
<input type="file" id="videoFile" accept="video/*">
</p>
<p>
<span>2. </span>
<button id="capture" disabled>Capture</button>
</p>
</div>
<div class="output">
<div>
<h3>Screen Share Preview</h3>
<br>
<video id="screeSharePreview" autoplay></video>
<br>
<button id="hide" disabled>Hide preview</button>
<button id="mute" disabled>Mute Local Audio</button>
<button id="duck" disabled>Duck Local Audio</button>
</div>
<div>
<h3>Remote Receiver</h3>
<label for="audioOutput">Audio Output Device:</label>
<select id="audioOutput" disabled></select>
<br>
<video id="receiver" autoplay></video>
</div>
</div>
<br>
<span></span>
<!-- sending peer -->
<script type="module">
const screenSharePreview = document.getElementById('screeSharePreview');
const fileChooser = document.getElementById('videoFile');
const bbbBtn = document.getElementById('bbb');
const captureBtn = document.querySelector('button#capture');
const hideButton = document.querySelector('button#hide');
const muteButton = document.querySelector('button#mute');
const duckButton = document.querySelector('button#duck');
const span = document.querySelector('span');
let videoUrl = null;
let newTab = null;
// Send the stream using WebRTC's RTCPeerConnection
async function sendVideo(stream) {
const pc = new RTCPeerConnection();
window.pc = pc;
stream.getTracks().forEach(track => pc.addTrack(track, stream));
pc.onicecandidate = candidate => {
const toReceiverEvent = new CustomEvent('candidate', {detail: candidate});
document.dispatchEvent(toReceiverEvent);
};
document.addEventListener('candidate', async e => {
// console.debug(e.detail);
if (e.detail.candidate)
await pc.addIceCandidate(e.detail.candidate);
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const toReceiverEvent = new CustomEvent('offer', {detail: offer});
document.dispatchEvent(toReceiverEvent);
document.addEventListener('answer', async e => {
console.debug(e.detail);
await pc.setRemoteDescription(e.detail);
});
}
// play the Big Buck Bunny video
bbbBtn.onclick = () => {
videoUrl = '../media/BigBuckBunny_360p30.mp4';
bbbBtn.disabled = true;
captureBtn.disabled = false;
fileChooser.disabled = true;
newTab = window.open('videoPlayer.html#' + encodeURIComponent(videoUrl), '_blank', 'popup=yes, scrollbars=no, width=640, height=412');
}
// let the user choose any video file (html above will accept only video files)
fileChooser.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) {
return;
}
videoUrl = URL.createObjectURL(file);
console.log(file);
captureBtn.disabled = false;
// race condition where the new window was opening before the file picker closed, which caused an error
setTimeout(() => {
// Open the video file in a new tab
// const newTab = window.open(videoUrl, '_blank', 'popup=yes, scrollbars=no, width=640, height=360');
newTab = window.open('videoPlayer.html#' + encodeURIComponent(videoUrl), '_blank', 'popup=yes, scrollbars=no width=640, height=412');
}, 1000);
});
// start screen capture
captureBtn.onclick = async () => {
if (!videoUrl) {
alert("Please select a video file first.");
return;
}
// Check if the new tab was blocked by the browser
if (!newTab) {
alert('failed to open tab');
return;
}
try {
// Request the user to select the tab to share
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
mediaSource: "tab", // this doesn't make a difference
width: {ideal: 640}, // set to the size of the hosted video
height: {ideal: 360}, // set to the size of the hosted video
cursor: "never", // this doesn't work
},
audio: {
suppressLocalAudioPlayback: true, // works on chromium
noiseSuppression: false,
echoCancellation: false,
autoGainControl: false,
},
selfBrowserSurface: "exclude", // works on chromium
monitorTypeSurfaces: "exclude", // works on chromium
preferCurrentTab: false, // default
});
window.stream = stream;
console.log(stream);
screenSharePreview.srcObject = stream;
// Check the track settings
span.innerHTML = `Stream video settings: ${JSON.stringify(stream.getVideoTracks()[0].getSettings())}<br>`;
if (stream.getAudioTracks().length > 0)
span.innerHTML += `Stream audio settings: ${JSON.stringify(stream.getAudioTracks()[0].getSettings())}`;
else
span.innerHTML += `😢 stream has no audio`;
// Send it to the remote
await sendVideo(stream);
hideButton.disabled = false;
muteButton.disabled = false;
duckButton.disabled = false;
} catch (error) {
console.error('Error accessing display media: ', error);
}
}
// Local video controls
hideButton.onclick = () => {
screenSharePreview.hidden = !screenSharePreview.hidden;
hideButton.innerText = screenSharePreview.hidden ? "Show preview" : "Hide preview";
};
muteButton.onclick = () => {
screenSharePreview.muted = !screenSharePreview.muted;
muteButton.innerText = screenSharePreview.muted ? "Unmute local video" : "Mute local video";
};
duckButton.onclick = () => {
screenSharePreview.volume = screenSharePreview.volume === 0.1 ? 1.0 : 0.1;
duckButton.innerText = screenSharePreview.volume === 0.1 ? "Unduck local video" : "Duck local video";
};
</script>
<!-- RECEIVER -->
<script>
const receiverVideo = document.querySelector('video#receiver');
// Enumerate audio output devices and populate the dropdown
async function populateAudioOutputDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioOutputDevices = devices.filter(device => device.kind === 'audiooutput');
const audioOutputSelect = document.getElementById('audioOutput');
audioOutputSelect.disabled = false;
// remove all existing child elements
while (audioOutputSelect.firstChild) {
audioOutputSelect.removeChild(audioOutputSelect.firstChild);
}
audioOutputDevices.forEach(device => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Device ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
});
// Change the audio output device when a new device is selected
audioOutputSelect.addEventListener('change', async (event) => {
const deviceId = event.target.value;
try {
await receiverVideo.setSinkId(deviceId);
console.log(`Audio output device set to ${deviceId}`);
} catch (error) {
console.error('Failed to set audio output device:', error);
}
});
}
receiverVideo.onplaying = async () => {
console.log("video playing stream:", receiverVideo.srcObject);
await populateAudioOutputDevices();
}
// Look for an offer event to start the peerConnection and answer
document.addEventListener('offer', async e => {
console.debug(e.detail);
const pc = new RTCPeerConnection();
pc.ontrack = e => {
console.debug(e);
const stream = e.streams[0];
receiverVideo.srcObject = stream;
window.receiveStream = stream;
};
pc.onicecandidate = candidate => {
const toReceiverEvent = new CustomEvent('candidate', {detail: candidate});
document.dispatchEvent(toReceiverEvent);
};
document.addEventListener('candidate', async e => {
console.debug(e.detail);
await pc.addIceCandidate(e.detail.candidate);
});
await pc.setRemoteDescription(e.detail);
window.receiverPc = pc;
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
const toSenderEvent = new CustomEvent('answer', {detail: answer});
document.dispatchEvent(toSenderEvent);
});
</script>
</body>
</html>