feat: implement play/pause/seek sync
This commit is contained in:
commit
36d9211dce
8 changed files with 625 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2024 sup39
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# sup-ytsync
|
||||||
|
A tampermonkey script to sync Youtube video progress with others via MQTT
|
218
index.js
Normal file
218
index.js
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
// ==UserScript==
|
||||||
|
// @name sup-ytsync
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 0.1.0
|
||||||
|
// @description sup39
|
||||||
|
// @author sup39
|
||||||
|
// @match https://www.youtube.com/watch*
|
||||||
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
|
||||||
|
// @grant none
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const url = '...';
|
||||||
|
const username = '...';
|
||||||
|
const password = '...';
|
||||||
|
const roomId = '...';
|
||||||
|
const clientId = `ytsync-${username}-${+new Date()}`;
|
||||||
|
// topic = ${topicPrefixBase}/${roomId}/${videoId}/${commander.clientId}/${command}
|
||||||
|
const topicPrefixBase = 'sup-ytsync';
|
||||||
|
|
||||||
|
const mqttScriptUrl = 'https://unpkg.com/mqtt@5.3.4/dist/mqtt.min.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLVideoElement} element
|
||||||
|
* @param {VideoCommandBroadcaster} commandBroadcaster
|
||||||
|
*/
|
||||||
|
function Video(element, commandBroadcaster) {
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
const queuedCommands = new Set();
|
||||||
|
|
||||||
|
for (const [eventName, command] of (/**@type{const}*/([
|
||||||
|
['play', 'play'],
|
||||||
|
['pause', 'pause'],
|
||||||
|
['seeked', 'seek'],
|
||||||
|
]))) {
|
||||||
|
element.addEventListener(eventName, () => {
|
||||||
|
// ignore queued command
|
||||||
|
if (queuedCommands.delete(command)) return;
|
||||||
|
// ignore seeked event when playing
|
||||||
|
if (eventName === 'seeked' && !element.paused) return;
|
||||||
|
// callback
|
||||||
|
const {currentTime} = element;
|
||||||
|
commandBroadcaster(command, {currentTime});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/** @param {number} t */
|
||||||
|
seek(t) {
|
||||||
|
queuedCommands.add('seek');
|
||||||
|
element.currentTime = t;
|
||||||
|
},
|
||||||
|
play() {
|
||||||
|
queuedCommands.add('play');
|
||||||
|
element.play();
|
||||||
|
},
|
||||||
|
pause() {
|
||||||
|
queuedCommands.add('pause');
|
||||||
|
element.pause();
|
||||||
|
},
|
||||||
|
get isPaused() {
|
||||||
|
return element.paused;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
const Buffer = /** @type{Buffer}*//**@type{any}*/(Uint8Array);
|
||||||
|
|
||||||
|
const videoId = new URLSearchParams(window.location.search).get('v');
|
||||||
|
if (videoId == null) return;
|
||||||
|
|
||||||
|
const videoElement = document.querySelector('video');
|
||||||
|
if (videoElement == null) return;
|
||||||
|
|
||||||
|
/** @returns {Promise<void>} */
|
||||||
|
const loadMqttScript = () => new Promise(resolve => {
|
||||||
|
if (typeof mqtt === 'object') return resolve();
|
||||||
|
document.head.appendChild(
|
||||||
|
Object.assign(document.createElement('script'), {
|
||||||
|
src: mqttScriptUrl,
|
||||||
|
async: true,
|
||||||
|
}),
|
||||||
|
).addEventListener('load', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
const startYtsync = () => loadMqttScript().then(() => {
|
||||||
|
const topicPrefix = `${topicPrefixBase}/${roomId}/${videoId}/`;
|
||||||
|
|
||||||
|
console.info('Connecting to MQTT server:', url);
|
||||||
|
const client = mqtt.connect(url, {username, password, clientId});
|
||||||
|
client.on('error', e => console.warn('Error occurs on MQTT connection:', e));
|
||||||
|
|
||||||
|
/** @param {DataView} body */
|
||||||
|
function parseTime(body) {
|
||||||
|
if (body.byteLength !== 8) {
|
||||||
|
throw new Error('Invalid payload');
|
||||||
|
}
|
||||||
|
return body.getFloat64(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on('connect', () => {
|
||||||
|
console.info('Connected to MQTT server:', url);
|
||||||
|
|
||||||
|
const video = Video(videoElement, (command, state) => {
|
||||||
|
const {currentTime: t} = state;
|
||||||
|
console.debug(`Broadcast video sync command [${command}]`, t);
|
||||||
|
const payload = new Buffer(8);
|
||||||
|
new DataView(payload.buffer).setFloat64(0, t);
|
||||||
|
client.publish(`${topicPrefix}${clientId}/${command}`, payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('message', (/**@type{string}*/topic, /**@type{Uint8Array}*/payload) => {
|
||||||
|
if (!topic.startsWith(topicPrefix)) {
|
||||||
|
return console.warn('Unexpected MQTT message', {topic, payload});
|
||||||
|
}
|
||||||
|
const [commander, command=''] = topic.slice(topicPrefix.length).split('/');
|
||||||
|
// ignore command sent by self
|
||||||
|
if (commander === clientId) return;
|
||||||
|
|
||||||
|
/** DataView of payload */
|
||||||
|
const body = new DataView(
|
||||||
|
payload.buffer,
|
||||||
|
payload.byteOffset,
|
||||||
|
payload.byteLength,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (command === 'play') {
|
||||||
|
const t = parseTime(body);
|
||||||
|
console.debug(`Received command [${command}]`, t);
|
||||||
|
video.seek(t);
|
||||||
|
video.play();
|
||||||
|
} else if (command === 'pause') {
|
||||||
|
const t = parseTime(body);
|
||||||
|
console.debug(`Received command [${command}]`, t);
|
||||||
|
video.seek(t);
|
||||||
|
video.pause();
|
||||||
|
} else if (command === 'seek') {
|
||||||
|
/**
|
||||||
|
* (seek while playing) pause -> play -> seeked
|
||||||
|
* => no need to update current time
|
||||||
|
* (seek when paused) pause -> seeked
|
||||||
|
* => need to update current time
|
||||||
|
*/
|
||||||
|
if (video.isPaused) {
|
||||||
|
const t = parseTime(body);
|
||||||
|
console.debug(`Received command [${command}]`, t);
|
||||||
|
video.seek(t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return console.warn('Unknown command', {command, payload});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e, {topic, payload});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.subscribe(topicPrefix+'+/+');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Based on https://stackoverflow.com/a/61511955 by Yong Wang (2020)
|
||||||
|
/**
|
||||||
|
* @param {string} selector
|
||||||
|
* @returns {Promise<Element>}
|
||||||
|
*/
|
||||||
|
const untilElementLoaded = selector => new Promise(resolve => {
|
||||||
|
const elm = document.querySelector(selector);
|
||||||
|
if (elm != null) return elm;
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const elm = document.querySelector(selector);
|
||||||
|
if (elm != null) {
|
||||||
|
observer.disconnect();
|
||||||
|
resolve(elm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO better UI
|
||||||
|
untilElementLoaded('#title h1').then(elm => elm.appendChild(
|
||||||
|
Object.assign(document.createElement('button'), {
|
||||||
|
textContent: 'Start sup-ytsync'
|
||||||
|
}),
|
||||||
|
).addEventListener('click', startYtsync, {once: true}));
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 sup39
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
6
jsconfig.json
Normal file
6
jsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"checkJs": true,
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
11
package.json
Normal file
11
package.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "sup-ytsync",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "A tampermonkey script to sync Youtube video progress with others via MQTT",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "sup39",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"mqtt": "^5.3.4"
|
||||||
|
}
|
||||||
|
}
|
353
pnpm-lock.yaml
Normal file
353
pnpm-lock.yaml
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
devDependencies:
|
||||||
|
mqtt:
|
||||||
|
specifier: ^5.3.4
|
||||||
|
version: 5.3.4
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
/@babel/runtime@7.23.8:
|
||||||
|
resolution: {integrity: sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: 0.14.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/node@20.11.5:
|
||||||
|
resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==}
|
||||||
|
dependencies:
|
||||||
|
undici-types: 5.26.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/readable-stream@4.0.10:
|
||||||
|
resolution: {integrity: sha512-AbUKBjcC8SHmImNi4yK2bbjogQlkFSg7shZCcicxPQapniOlajG8GCc39lvXzCWX4lLRRs7DM3VAeSlqmEVZUA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.11.5
|
||||||
|
safe-buffer: 5.1.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/ws@8.5.10:
|
||||||
|
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.11.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/abort-controller@3.0.0:
|
||||||
|
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||||
|
engines: {node: '>=6.5'}
|
||||||
|
dependencies:
|
||||||
|
event-target-shim: 5.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/balanced-match@1.0.2:
|
||||||
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/base64-js@1.5.1:
|
||||||
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/bl@6.0.10:
|
||||||
|
resolution: {integrity: sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==}
|
||||||
|
dependencies:
|
||||||
|
buffer: 6.0.3
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 4.5.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/brace-expansion@2.0.1:
|
||||||
|
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||||
|
dependencies:
|
||||||
|
balanced-match: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/buffer-from@1.1.2:
|
||||||
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/buffer@6.0.3:
|
||||||
|
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/commist@3.2.0:
|
||||||
|
resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/concat-stream@2.0.0:
|
||||||
|
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
|
||||||
|
engines: {'0': node >= 6.0}
|
||||||
|
dependencies:
|
||||||
|
buffer-from: 1.1.2
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
typedarray: 0.0.6
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/debug@4.3.4:
|
||||||
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/event-target-shim@5.0.1:
|
||||||
|
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/events@3.3.0:
|
||||||
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
|
engines: {node: '>=0.8.x'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/fast-unique-numbers@8.0.13:
|
||||||
|
resolution: {integrity: sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==}
|
||||||
|
engines: {node: '>=16.1.0'}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.23.8
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/fs.realpath@1.0.0:
|
||||||
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/glob@8.1.0:
|
||||||
|
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dependencies:
|
||||||
|
fs.realpath: 1.0.0
|
||||||
|
inflight: 1.0.6
|
||||||
|
inherits: 2.0.4
|
||||||
|
minimatch: 5.1.6
|
||||||
|
once: 1.4.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/help-me@4.2.0:
|
||||||
|
resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==}
|
||||||
|
dependencies:
|
||||||
|
glob: 8.1.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/ieee754@1.2.1:
|
||||||
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/inflight@1.0.6:
|
||||||
|
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
wrappy: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/inherits@2.0.4:
|
||||||
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/js-sdsl@4.3.0:
|
||||||
|
resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/lru-cache@10.1.0:
|
||||||
|
resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==}
|
||||||
|
engines: {node: 14 || >=16.14}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/minimatch@5.1.6:
|
||||||
|
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/minimist@1.2.8:
|
||||||
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/mqtt-packet@9.0.0:
|
||||||
|
resolution: {integrity: sha512-8v+HkX+fwbodsWAZIZTI074XIoxVBOmPeggQuDFCGg1SqNcC+uoRMWu7J6QlJPqIUIJXmjNYYHxBBLr1Y/Df4w==}
|
||||||
|
dependencies:
|
||||||
|
bl: 6.0.10
|
||||||
|
debug: 4.3.4
|
||||||
|
process-nextick-args: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/mqtt@5.3.4:
|
||||||
|
resolution: {integrity: sha512-nyhr2bnFtyiv68jV3yfR6eQtGcGs/jr2l3ETKXYc0amttsasXa1KgvETHRNRjfeDt/yc68IqoEjFzKkHpoQUPQ==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
'@types/readable-stream': 4.0.10
|
||||||
|
'@types/ws': 8.5.10
|
||||||
|
commist: 3.2.0
|
||||||
|
concat-stream: 2.0.0
|
||||||
|
debug: 4.3.4
|
||||||
|
help-me: 4.2.0
|
||||||
|
lru-cache: 10.1.0
|
||||||
|
minimist: 1.2.8
|
||||||
|
mqtt-packet: 9.0.0
|
||||||
|
number-allocator: 1.0.14
|
||||||
|
readable-stream: 4.5.2
|
||||||
|
reinterval: 1.1.0
|
||||||
|
rfdc: 1.3.1
|
||||||
|
split2: 4.2.0
|
||||||
|
worker-timers: 7.1.1
|
||||||
|
ws: 8.16.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/ms@2.1.2:
|
||||||
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/number-allocator@1.0.14:
|
||||||
|
resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==}
|
||||||
|
dependencies:
|
||||||
|
debug: 4.3.4
|
||||||
|
js-sdsl: 4.3.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/once@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
dependencies:
|
||||||
|
wrappy: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/process-nextick-args@2.0.1:
|
||||||
|
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/process@0.11.10:
|
||||||
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
|
engines: {node: '>= 0.6.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/readable-stream@3.6.2:
|
||||||
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/readable-stream@4.5.2:
|
||||||
|
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
buffer: 6.0.3
|
||||||
|
events: 3.3.0
|
||||||
|
process: 0.11.10
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/regenerator-runtime@0.14.1:
|
||||||
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/reinterval@1.1.0:
|
||||||
|
resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/rfdc@1.3.1:
|
||||||
|
resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/safe-buffer@5.1.2:
|
||||||
|
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/safe-buffer@5.2.1:
|
||||||
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/split2@4.2.0:
|
||||||
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
|
engines: {node: '>= 10.x'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/string_decoder@1.3.0:
|
||||||
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/tslib@2.6.2:
|
||||||
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/typedarray@0.0.6:
|
||||||
|
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/undici-types@5.26.5:
|
||||||
|
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/util-deprecate@1.0.2:
|
||||||
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/worker-timers-broker@6.1.1:
|
||||||
|
resolution: {integrity: sha512-CTlDnkXAewtYvw5gOwVIc6UuIPcNHJrqWxBMhZbCWOmadvl20nPs9beAsXlaTEwW3G2KBpuKiSgkhBkhl3mxDA==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.23.8
|
||||||
|
fast-unique-numbers: 8.0.13
|
||||||
|
tslib: 2.6.2
|
||||||
|
worker-timers-worker: 7.0.65
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/worker-timers-worker@7.0.65:
|
||||||
|
resolution: {integrity: sha512-Dl4nGONr8A8Fr+vQnH7Ee+o2iB480S1fBcyJYqnMyMwGRVyQZLZU+o91vbMvU1vHqiryRQmjXzzMYlh86wx+YQ==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.23.8
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/worker-timers@7.1.1:
|
||||||
|
resolution: {integrity: sha512-axtq83GwPqYwkQmQmei2abQ9cT7oSwmLw4lQCZ9VmMH9g4t4kuEF1Gw+tdnIJGHCiZ2QPDnr/+307bYx6tynLA==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.23.8
|
||||||
|
tslib: 2.6.2
|
||||||
|
worker-timers-broker: 6.1.1
|
||||||
|
worker-timers-worker: 7.0.65
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/wrappy@1.0.2:
|
||||||
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/ws@8.16.0:
|
||||||
|
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
dev: true
|
13
types.d.ts
vendored
Normal file
13
types.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import MQTT from 'mqtt';
|
||||||
|
declare global {
|
||||||
|
const mqtt: typeof MQTT;
|
||||||
|
|
||||||
|
type VideoCommand = 'play' | 'pause' | 'seek';
|
||||||
|
type VideoState = {
|
||||||
|
currentTime: number
|
||||||
|
};
|
||||||
|
type VideoCommandBroadcaster = (
|
||||||
|
command: VideoCommand,
|
||||||
|
state: VideoState
|
||||||
|
) => void;
|
||||||
|
}
|
Loading…
Reference in a new issue