mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
Added websocket wrapper for TCP connection to Android receiver.
This commit is contained in:
parent
b339f4f487
commit
ad8f3985a3
22 changed files with 1165 additions and 277 deletions
375
clients/chrome/background.js
Normal file
375
clients/chrome/background.js
Normal file
|
@ -0,0 +1,375 @@
|
|||
let mediaUrls = [];
|
||||
let hosts = [];
|
||||
let currentWebSocket = null;
|
||||
let playbackState = null;
|
||||
let volume = 1.0;
|
||||
let selectedHost = null;
|
||||
|
||||
const Opcode = {
|
||||
None: 0,
|
||||
Play: 1,
|
||||
Pause: 2,
|
||||
Resume: 3,
|
||||
Stop: 4,
|
||||
Seek: 5,
|
||||
PlaybackUpdate: 6,
|
||||
VolumeUpdate: 7,
|
||||
SetVolume: 8,
|
||||
};
|
||||
|
||||
chrome.runtime.onInstalled.addListener(function() {
|
||||
console.log("onInstalled");
|
||||
chrome.storage.local.get(['hosts', 'selectedHost'], function(result) {
|
||||
console.log("load persistence", result);
|
||||
|
||||
hosts = result.hosts || [];
|
||||
selectedHost = result.selectedHost || null;
|
||||
|
||||
if (selectedHost) {
|
||||
maintainWebSocketConnection(selectedHost)
|
||||
}
|
||||
notifyPopup('updateHosts');
|
||||
notifyPopup('updateUrls');
|
||||
});
|
||||
});
|
||||
|
||||
chrome.webRequest.onHeadersReceived.addListener(
|
||||
function(details) {
|
||||
console.log(`onHeadersReceived (${details.url})`, details);
|
||||
const contentType = details.responseHeaders.find(header => header.name.toLowerCase() === 'content-type')?.value;
|
||||
if (!contentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isMedia = contentType.startsWith('video/') ||
|
||||
contentType.startsWith('audio/') ||
|
||||
contentType.toLowerCase() == "application/x-mpegurl" ||
|
||||
contentType.toLowerCase() == "application/dash+xml";
|
||||
const isSegment = details.url.endsWith(".ts");
|
||||
|
||||
if (contentType && isMedia && !isSegment) {
|
||||
if (!mediaUrls.some(v => v.url === details.url))
|
||||
mediaUrls.push({contentType, url: details.url});
|
||||
console.log('Media URL found:', {contentType, url: details.url});
|
||||
notifyPopup('updateUrls');
|
||||
}
|
||||
},
|
||||
{ urls: ["<all_urls>"] },
|
||||
["responseHeaders"]
|
||||
);
|
||||
|
||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
if (request.action === 'getUrls') {
|
||||
sendResponse({ urls: mediaUrls, selectedHost });
|
||||
} else if (request.action === 'clearAll') {
|
||||
mediaUrls = [];
|
||||
notifyPopup('updateUrls');
|
||||
} else if (request.action === 'deleteUrl') {
|
||||
mediaUrls = mediaUrls.filter(url => url !== request.url);
|
||||
notifyPopup('updateUrls');
|
||||
} else if (request.action === 'addHost') {
|
||||
hosts.push(request.host);
|
||||
chrome.storage.local.set({ 'hosts': hosts }, function () {
|
||||
console.log('Hosts saved', hosts);
|
||||
});
|
||||
notifyPopup('updateHosts');
|
||||
} else if (request.action === 'selectHost') {
|
||||
selectedHost = request.host;
|
||||
chrome.storage.local.set({ 'selectedHost': selectedHost }, function () {
|
||||
console.log('Selected host saved', selectedHost);
|
||||
});
|
||||
|
||||
maintainWebSocketConnection(selectedHost);
|
||||
notifyPopup('updateHosts');
|
||||
notifyPopup('updateUrls');
|
||||
} else if (request.action === 'deleteHost') {
|
||||
hosts = hosts.filter(host => host !== request.host);
|
||||
if (selectedHost === request.host) {
|
||||
selectedHost = null;
|
||||
chrome.storage.local.set({ 'selectedHost': selectedHost }, function () {
|
||||
console.log('Selected host cleared');
|
||||
});
|
||||
}
|
||||
|
||||
chrome.storage.local.set({ 'hosts': hosts }, function () {
|
||||
console.log('Hosts updated after deletion');
|
||||
});
|
||||
notifyPopup('updateHosts');
|
||||
notifyPopup('updateUrls');
|
||||
} else if (request.action === 'castVideo') {
|
||||
play(selectedHost, {
|
||||
container: request.url.contentType,
|
||||
url: request.url.url
|
||||
});
|
||||
} else if (request.action === 'getHosts') {
|
||||
sendResponse({ hosts, selectedHost });
|
||||
} else if (request.action == 'getPlaybackState') {
|
||||
sendResponse({ selectedHost, playbackState });
|
||||
} else if (request.action == 'getVolume') {
|
||||
sendResponse({ volume });
|
||||
} else if (request.action === 'resume') {
|
||||
resume(selectedHost);
|
||||
} else if (request.action === 'pause') {
|
||||
pause(selectedHost);
|
||||
} else if (request.action === 'stop') {
|
||||
stop(selectedHost);
|
||||
} else if (request.action === 'setVolume') {
|
||||
setVolume(selectedHost, request.volume);
|
||||
} else if (request.action === 'seek') {
|
||||
seek(selectedHost, request.time);
|
||||
}
|
||||
});
|
||||
|
||||
function closeCurrentWebSocket() {
|
||||
if (currentWebSocket) {
|
||||
console.log('Closing current WebSocket connection');
|
||||
currentWebSocket.close();
|
||||
currentWebSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
function notifyPopup(action) {
|
||||
chrome.runtime.sendMessage({ action: action });
|
||||
}
|
||||
|
||||
function maintainWebSocketConnection(host) {
|
||||
closeCurrentWebSocket();
|
||||
|
||||
if (!host) {
|
||||
console.log('No host selected, stopping WebSocket connection');
|
||||
return;
|
||||
}
|
||||
|
||||
let hostAddress, port;
|
||||
const portIndex = host.indexOf(':');
|
||||
if (portIndex === -1) {
|
||||
hostAddress = host;
|
||||
port = 46899;
|
||||
} else {
|
||||
hostAddress = host.substring(0, portIndex);
|
||||
port = host.substring(portIndex + 1, host.length);
|
||||
}
|
||||
|
||||
const wsUrl = `ws://${hostAddress}:${port}`;
|
||||
currentWebSocket = new WebSocket(wsUrl);
|
||||
|
||||
currentWebSocket.onopen = function() {
|
||||
console.log('WebSocket connection opened to ' + wsUrl);
|
||||
};
|
||||
|
||||
currentWebSocket.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
currentWebSocket.onclose = function(event) {
|
||||
console.log('WebSocket connection closed:', event.reason);
|
||||
if (selectedHost === host) {
|
||||
console.log('Attempting to reconnect...');
|
||||
setTimeout(() => maintainWebSocketConnection(host), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const LENGTH_BYTES = 4;
|
||||
const MAXIMUM_PACKET_LENGTH = 32 * 1024;
|
||||
const SessionState = {
|
||||
WaitingForLength: 0,
|
||||
WaitingForData: 1
|
||||
};
|
||||
|
||||
let state = SessionState.WaitingForLength;
|
||||
let packetLength = 0;
|
||||
let bytesRead = 0;
|
||||
let buffer = new Uint8Array(MAXIMUM_PACKET_LENGTH);
|
||||
|
||||
function handleLengthBytes(dataView, offset, count) {
|
||||
let bytesToRead = Math.min(LENGTH_BYTES - bytesRead, count);
|
||||
let bytesRemaining = count - bytesToRead;
|
||||
for (let i = 0; i < bytesToRead; i++) {
|
||||
buffer[bytesRead + i] = dataView.getUint8(offset + i);
|
||||
}
|
||||
bytesRead += bytesToRead;
|
||||
|
||||
if (bytesRead >= LENGTH_BYTES) {
|
||||
packetLength = dataView.getUint32(0, true); // true for little-endian
|
||||
bytesRead = 0;
|
||||
state = SessionState.WaitingForData;
|
||||
|
||||
if (packetLength > MAXIMUM_PACKET_LENGTH) {
|
||||
throw new Error("Maximum packet length exceeded");
|
||||
}
|
||||
|
||||
if (bytesRemaining > 0) {
|
||||
handlePacketBytes(dataView, offset + bytesToRead, bytesRemaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handlePacketBytes(dataView, offset, count) {
|
||||
let bytesToRead = Math.min(packetLength - bytesRead, count);
|
||||
let bytesRemaining = count - bytesToRead;
|
||||
for (let i = 0; i < bytesToRead; i++) {
|
||||
buffer[bytesRead + i] = dataView.getUint8(offset + i);
|
||||
}
|
||||
bytesRead += bytesToRead;
|
||||
|
||||
if (bytesRead >= packetLength) {
|
||||
handlePacket();
|
||||
|
||||
state = SessionState.WaitingForLength;
|
||||
packetLength = 0;
|
||||
bytesRead = 0;
|
||||
|
||||
if (bytesRemaining > 0) {
|
||||
handleLengthBytes(dataView, offset + bytesToRead, bytesRemaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handlePacket() {
|
||||
console.log(`Processing packet of ${bytesRead} bytes`);
|
||||
|
||||
// Parse opcode and body
|
||||
const opcode = buffer[0];
|
||||
const body = bytesRead > 1 ? new TextDecoder().decode(buffer.slice(1, bytesRead)) : null;
|
||||
|
||||
console.log("Received body:", body);
|
||||
|
||||
switch (opcode) {
|
||||
case Opcode.PlaybackUpdate:
|
||||
if (body) {
|
||||
try {
|
||||
const playbackUpdateMsg = JSON.parse(body);
|
||||
console.log("Received playback update", playbackUpdateMsg);
|
||||
playbackState = playbackUpdateMsg;
|
||||
notifyPopup('updatePlaybackState');
|
||||
} catch (error) {
|
||||
console.error("Error parsing playback update message:", error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Opcode.VolumeUpdate:
|
||||
if (body) {
|
||||
try {
|
||||
const volumeUpdateMsg = JSON.parse(body);
|
||||
console.log("Received volume update", volumeUpdateMsg);
|
||||
volume = volumeUpdateMsg;
|
||||
notifyPopup('updateVolume');
|
||||
} catch (error) {
|
||||
console.error("Error parsing volume update message:", error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`Error handling packet`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
currentWebSocket.onmessage = function(event) {
|
||||
if (typeof event.data === "string") {
|
||||
console.log("Text message received, not handled:", event.data);
|
||||
} else {
|
||||
event.data.arrayBuffer().then((buffer) => {
|
||||
let dataView = new DataView(buffer);
|
||||
if (state === SessionState.WaitingForLength) {
|
||||
handleLengthBytes(dataView, 0, buffer.byteLength);
|
||||
} else if (state === SessionState.WaitingForData) {
|
||||
handlePacketBytes(dataView, 0, buffer.byteLength);
|
||||
} else {
|
||||
console.error("Invalid state encountered");
|
||||
maintainWebSocketConnection(host);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function sendWebSocketPacket(h, packet) {
|
||||
let host;
|
||||
let port;
|
||||
const portIndex = h.indexOf(':');
|
||||
if (portIndex == -1) {
|
||||
host = h;
|
||||
port = 46899;
|
||||
} else {
|
||||
host = h.substring(0, portIndex);
|
||||
port = h.substring(portIndex + 1, h.length);
|
||||
}
|
||||
|
||||
const wsUrl = `ws://${host}:${port}`;
|
||||
const socket = new WebSocket(wsUrl);
|
||||
socket.onopen = function() {
|
||||
console.log('Connection opened to ' + wsUrl);
|
||||
|
||||
socket.send(packet);
|
||||
socket.close();
|
||||
console.log('Connection closed after sending packet');
|
||||
};
|
||||
|
||||
socket.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
socket.onclose = function (event) {
|
||||
console.log('WebSocket connection closed:', event.reason);
|
||||
};
|
||||
}
|
||||
|
||||
function createHeader(opcode, bodyLength) {
|
||||
const buffer = new ArrayBuffer(5);
|
||||
const view = new DataView(buffer);
|
||||
view.setUint32(0, bodyLength + 1, true); // size (little endian)
|
||||
view.setUint8(4, opcode);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function createBody(jsonObject) {
|
||||
const jsonString = JSON.stringify(jsonObject);
|
||||
return new TextEncoder().encode(jsonString);
|
||||
}
|
||||
|
||||
function play(host, playMessage) {
|
||||
const body = createBody(playMessage);
|
||||
const header = createHeader(1, body.length);
|
||||
const packet = concatenateBuffers(header, body);
|
||||
sendWebSocketPacket(host, packet);
|
||||
}
|
||||
|
||||
function pause(host) {
|
||||
const header = createHeader(2, 0);
|
||||
sendWebSocketPacket(host, new Uint8Array(header));
|
||||
}
|
||||
|
||||
function resume(host) {
|
||||
const header = createHeader(3, 0);
|
||||
sendWebSocketPacket(host, new Uint8Array(header));
|
||||
}
|
||||
|
||||
function stop(host) {
|
||||
const header = createHeader(4, 0);
|
||||
sendWebSocketPacket(host, new Uint8Array(header));
|
||||
}
|
||||
|
||||
function seek(host, time) {
|
||||
const body = createBody({time});
|
||||
const header = createHeader(5, body.length);
|
||||
const packet = concatenateBuffers(header, body);
|
||||
sendWebSocketPacket(host, packet);
|
||||
}
|
||||
|
||||
function setVolume(host, volume) {
|
||||
const body = createBody({volume});
|
||||
const header = createHeader(8, body.length);
|
||||
const packet = concatenateBuffers(header, body);
|
||||
sendWebSocketPacket(host, packet);
|
||||
}
|
||||
|
||||
function concatenateBuffers(buffer1, buffer2) {
|
||||
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
|
||||
tmp.set(new Uint8Array(buffer1), 0);
|
||||
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
|
||||
return tmp.buffer;
|
||||
}
|
BIN
clients/chrome/icons/icon128.png
Normal file
BIN
clients/chrome/icons/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 926 B |
BIN
clients/chrome/icons/icon16.png
Normal file
BIN
clients/chrome/icons/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 489 B |
BIN
clients/chrome/icons/icon48.png
Normal file
BIN
clients/chrome/icons/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 595 B |
23
clients/chrome/manifest.json
Normal file
23
clients/chrome/manifest.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Video URL Collector",
|
||||
"version": "1.0",
|
||||
"permissions": [
|
||||
"webRequest",
|
||||
"storage"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
}
|
||||
}
|
66
clients/chrome/popup.html
Normal file
66
clients/chrome/popup.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Video URLs</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 10px; width: 400px; }
|
||||
ul { list-style-type: none; padding: 0; }
|
||||
li { margin: 5px 0; word-break: break-all; }
|
||||
|
||||
button {
|
||||
margin: 5px;
|
||||
padding: 5px 10px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-red {
|
||||
background-color: #AF4C50;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.host-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.url-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Hosts:</h3>
|
||||
<ul id="hostList"></ul>
|
||||
<button id="addHost">Add Host</button>
|
||||
<button id="clearAll">Clear All</button>
|
||||
|
||||
<h3>Controls:</h3>
|
||||
<div id="timeBarControls" style="opacity: 0.5;">
|
||||
<input type="range" id="timeBar" min="0" max="100" value="0" style="width: 100%;">
|
||||
<br>
|
||||
<button id="resumeButton" disabled>Resume</button>
|
||||
<button id="pauseButton" disabled>Pause</button>
|
||||
<button id="stopButton" disabled>Stop</button>
|
||||
<input type="range" id="volumeControl" min="0" max="100" value="50" disabled>
|
||||
</div>
|
||||
|
||||
<h3>Collected Video URLs:</h3>
|
||||
<ul id="urlList"></ul>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
228
clients/chrome/popup.js
Normal file
228
clients/chrome/popup.js
Normal file
|
@ -0,0 +1,228 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateUrlList();
|
||||
updateHostList();
|
||||
updateVolume();
|
||||
updatePlaybackState();
|
||||
|
||||
document.getElementById('clearAll').addEventListener('click', function() {
|
||||
chrome.runtime.sendMessage({ action: 'clearAll' });
|
||||
});
|
||||
|
||||
document.getElementById('addHost').addEventListener('click', function() {
|
||||
const host = prompt('Enter new host (ip:port):');
|
||||
if (host) {
|
||||
chrome.runtime.sendMessage({ action: 'addHost', host: host });
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
if (request.action === 'updateUrls') {
|
||||
updateUrlList();
|
||||
} else if (request.action === 'updateHosts') {
|
||||
updateHostList();
|
||||
} else if (request.action == 'updateVolume') {
|
||||
updateVolume();
|
||||
} else if (request.action == 'updatePlaybackState') {
|
||||
updatePlaybackState();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function updateUrlList() {
|
||||
console.log("updateUrlList");
|
||||
|
||||
chrome.runtime.sendMessage({ action: 'getUrls' }, function(response) {
|
||||
console.log("getUrls response", response);
|
||||
|
||||
const urlList = document.getElementById('urlList');
|
||||
urlList.innerHTML = '';
|
||||
response.urls.forEach(url => {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.classList.add('url-item');
|
||||
|
||||
const urlText = document.createElement('div');
|
||||
urlText.textContent = url.url;
|
||||
|
||||
const buttonContainer = document.createElement('div');
|
||||
buttonContainer.classList.add('action-buttons');
|
||||
|
||||
const castButton = document.createElement('button');
|
||||
castButton.textContent = 'C';
|
||||
castButton.disabled = !response.selectedHost;
|
||||
castButton.addEventListener('click', function() {
|
||||
if (response.selectedHost) {
|
||||
chrome.runtime.sendMessage({ action: 'castVideo', url });
|
||||
}
|
||||
});
|
||||
buttonContainer.appendChild(castButton);
|
||||
|
||||
listItem.appendChild(urlText);
|
||||
listItem.appendChild(buttonContainer);
|
||||
|
||||
urlList.appendChild(listItem);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateHostList() {
|
||||
console.log("updateHostList");
|
||||
|
||||
chrome.runtime.sendMessage({ action: 'getHosts' }, function(response) {
|
||||
console.log("getHosts response", response);
|
||||
|
||||
const hostList = document.getElementById('hostList');
|
||||
hostList.innerHTML = '';
|
||||
console.log("response.hosts", response.hosts);
|
||||
response.hosts.forEach(host => {
|
||||
const listItem = document.createElement('li');
|
||||
if (host === response.selectedHost) {
|
||||
listItem.style.color = 'green';
|
||||
}
|
||||
|
||||
listItem.style.display = 'flex';
|
||||
listItem.style.justifyContent = 'space-between';
|
||||
listItem.style.alignItems = 'center';
|
||||
|
||||
const hostText = document.createElement('span');
|
||||
hostText.textContent = host;
|
||||
hostText.style.flexGrow = 1;
|
||||
listItem.appendChild(hostText);
|
||||
|
||||
const selectButton = document.createElement('button');
|
||||
if (host === response.selectedHost) {
|
||||
selectButton.textContent = 'Unselect';
|
||||
selectButton.classList.add('button-red');
|
||||
selectButton.addEventListener('click', function() {
|
||||
chrome.runtime.sendMessage({ action: 'selectHost', host: null });
|
||||
});
|
||||
} else {
|
||||
selectButton.textContent = 'Select';
|
||||
selectButton.addEventListener('click', function() {
|
||||
chrome.runtime.sendMessage({ action: 'selectHost', host: host });
|
||||
});
|
||||
}
|
||||
listItem.appendChild(selectButton);
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.textContent = 'Delete';
|
||||
deleteButton.addEventListener('click', function() {
|
||||
chrome.runtime.sendMessage({ action: 'deleteHost', host: host });
|
||||
});
|
||||
listItem.appendChild(deleteButton);
|
||||
|
||||
hostList.appendChild(listItem);
|
||||
});
|
||||
|
||||
const controlsDiv = document.getElementById('timeBarControls');
|
||||
const timeBar = document.getElementById('timeBar');
|
||||
const resumeButton = document.getElementById('resumeButton');
|
||||
const pauseButton = document.getElementById('pauseButton');
|
||||
const stopButton = document.getElementById('stopButton');
|
||||
const volumeControl = document.getElementById('volumeControl');
|
||||
|
||||
if (response.selectedHost) {
|
||||
controlsDiv.style.opacity = 1;
|
||||
timeBar.disabled = false;
|
||||
resumeButton.disabled = false;
|
||||
pauseButton.disabled = false;
|
||||
stopButton.disabled = false;
|
||||
volumeControl.disabled = false;
|
||||
|
||||
timeBar.addEventListener('input', handleSeek);
|
||||
resumeButton.addEventListener('click', handleResume);
|
||||
pauseButton.addEventListener('click', handlePause);
|
||||
stopButton.addEventListener('click', handleStop);
|
||||
volumeControl.addEventListener('input', handleVolumeChanged);
|
||||
} else {
|
||||
controlsDiv.style.opacity = 0.5;
|
||||
timeBar.disabled = true;
|
||||
resumeButton.disabled = true;
|
||||
pauseButton.disabled = true;
|
||||
stopButton.disabled = true;
|
||||
volumeControl.disabled = true;
|
||||
|
||||
timeBar.removeEventListener('input', handleSeek);
|
||||
resumeButton.removeEventListener('click', handleResume);
|
||||
pauseButton.removeEventListener('click', handlePause);
|
||||
stopButton.removeEventListener('click', handleStop);
|
||||
volumeControl.removeEventListener('input', handleVolumeChanged);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateVolume() {
|
||||
console.log("updateVolume");
|
||||
|
||||
chrome.runtime.sendMessage({ action: 'getVolume' }, function (response) {
|
||||
const volumeControl = document.getElementById('volumeControl');
|
||||
if (response.volume) {
|
||||
volumeControl.value = response.volume * 100;
|
||||
} else {
|
||||
volumeControl.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatePlaybackState() {
|
||||
console.log("updatePlaybackState");
|
||||
|
||||
chrome.runtime.sendMessage({ action: 'getPlaybackState' }, function (response) {
|
||||
const timeBar = document.getElementById('timeBar');
|
||||
const resumeButton = document.getElementById('resumeButton');
|
||||
const pauseButton = document.getElementById('pauseButton');
|
||||
const stopButton = document.getElementById('stopButton');
|
||||
const volumeControl = document.getElementById('volumeControl');
|
||||
|
||||
if (!response.selectedHost || !response.playbackState || response.playbackState.state === 0) {
|
||||
resumeButton.disabled = true;
|
||||
pauseButton.disabled = true;
|
||||
stopButton.disabled = true;
|
||||
timeBar.disabled = true;
|
||||
volumeControl.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
timeBar.max = response.playbackState.duration * 1000;
|
||||
timeBar.value = response.playbackState.time * 1000;
|
||||
|
||||
stopButton.disabled = false;
|
||||
timeBar.disabled = false;
|
||||
volumeControl.disabled = false;
|
||||
|
||||
switch (response.playbackState.state) {
|
||||
case 1: // Playing
|
||||
resumeButton.disabled = true;
|
||||
pauseButton.disabled = false;
|
||||
break;
|
||||
case 2: // Paused
|
||||
resumeButton.disabled = false;
|
||||
pauseButton.disabled = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleSeek(event) {
|
||||
console.log("handleSeek", event);
|
||||
chrome.runtime.sendMessage({ action: 'seek', time: parseFloat(event.target.value) / 1000.0 });
|
||||
}
|
||||
|
||||
function handleResume(event) {
|
||||
console.log("handleResume", event);
|
||||
chrome.runtime.sendMessage({ action: 'resume' });
|
||||
}
|
||||
|
||||
function handlePause(event) {
|
||||
console.log("handlePause", event);
|
||||
chrome.runtime.sendMessage({ action: 'pause' });
|
||||
}
|
||||
|
||||
function handleStop(event) {
|
||||
console.log("handleStop", event);
|
||||
chrome.runtime.sendMessage({ action: 'stop' });
|
||||
}
|
||||
|
||||
function handleVolumeChanged(event) {
|
||||
console.log("handleVolumeChanged", event);
|
||||
chrome.runtime.sendMessage({ action: 'setVolume', volume: parseFloat(event.target.value) / 100.0 });
|
||||
}
|
|
@ -146,7 +146,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
session.send_message(Opcode::Play, Some(play_message))?;
|
||||
} else if let Some(seek_matches) = matches.subcommand_matches("seek") {
|
||||
let seek_message = SeekMessage::new(match seek_matches.value_of("timestamp") {
|
||||
Some(s) => s.parse::<u64>()?,
|
||||
Some(s) => s.parse::<f64>()?,
|
||||
_ => return Err("Timestamp is required.".into())
|
||||
});
|
||||
println!("Sent seek {:?}", seek_message);
|
||||
|
|
|
@ -16,18 +16,19 @@ impl PlayMessage {
|
|||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct SeekMessage {
|
||||
pub time: u64,
|
||||
pub time: f64,
|
||||
}
|
||||
|
||||
impl SeekMessage {
|
||||
pub fn new(time: u64) -> Self {
|
||||
pub fn new(time: f64) -> Self {
|
||||
Self { time }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct PlaybackUpdateMessage {
|
||||
pub time: u64,
|
||||
pub time: f64,
|
||||
pub duration: f64,
|
||||
pub state: u8 //0 = None, 1 = Playing, 2 = Paused
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue