Compare commits

..

No commits in common. "23245dd54924401f042cc3c7e69d9042d046ee16" and "6cefa7392c407af36683d9ab88acb0815143459d" have entirely different histories.

13 changed files with 99 additions and 500 deletions

View File

@ -97,22 +97,6 @@ func (db *RemoteDatabase) CreateRemoteCommands(remoteId string, commandIds []str
return nil return nil
} }
func (db *RemoteDatabase) GetRemote(remoteId string) (Remote, error) {
var remote Remote
queryString := "SELECT id, title FROM Remotes WHERE id = ?"
row := db.Connection.QueryRow(queryString, remoteId)
error := row.Scan(&remote.Id, &remote.Title)
if error != nil {
return Remote{}, fmt.Errorf("error scanning remote: %s", error)
}
commands, error := db.GetCommandsByRemoteId(remoteId)
if error != nil {
return Remote{}, error
}
remote.Commands = commands
return remote, nil
}
func (db *RemoteDatabase) GetRemotes() ([]Remote, error) { func (db *RemoteDatabase) GetRemotes() ([]Remote, error) {
rows, error := db.Connection.Query("SELECT id, title FROM Remotes") rows, error := db.Connection.Query("SELECT id, title FROM Remotes")
if error != nil { if error != nil {

View File

@ -17,14 +17,6 @@ func (rm *RemoteManager) CreateRemote(remote d.Remote) (string, error) {
return rm.remoteDatabase.CreateRemote(remote) return rm.remoteDatabase.CreateRemote(remote)
} }
func (rm *RemoteManager) GetRemote(remoteID string) (d.Remote, error) {
remote, err := rm.remoteDatabase.GetRemote(remoteID)
if err != nil {
return d.Remote{}, err
}
return remote, nil
}
func (rm *RemoteManager) GetRemotes() ([]d.Remote, error) { func (rm *RemoteManager) GetRemotes() ([]d.Remote, error) {
remotes, err := rm.remoteDatabase.GetRemotes() remotes, err := rm.remoteDatabase.GetRemotes()
if err != nil { if err != nil {

View File

@ -5,7 +5,6 @@ import (
m "playback-device-server/management" m "playback-device-server/management"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
) )
type RemoteApiHandler struct { type RemoteApiHandler struct {
@ -17,7 +16,6 @@ func (r *RemoteApiHandler) Initialize(authenticator *Authenticator) {
r.router.Use(authenticator.Authenticate("/api/remotes", []string{})) r.router.Use(authenticator.Authenticate("/api/remotes", []string{}))
remotesApi := r.router.Group("/api/remotes") remotesApi := r.router.Group("/api/remotes")
remotesApi.GET("", r.handleGetRemotes) remotesApi.GET("", r.handleGetRemotes)
remotesApi.GET("/:id", r.handleGetRemote)
remotesApi.POST("", r.handleCreateRemote) remotesApi.POST("", r.handleCreateRemote)
remotesApi.DELETE("/:id", r.handleDelteRemote) remotesApi.DELETE("/:id", r.handleDelteRemote)
@ -44,17 +42,6 @@ func (r *RemoteApiHandler) handleCreateRemote(context echo.Context) error {
return context.JSON(200, remote) return context.JSON(200, remote)
} }
func (r *RemoteApiHandler) handleGetRemote(context echo.Context) error {
id := context.Param("id")
remote, err := r.remoteManager.GetRemote(id)
if err != nil {
SendError(500, context, err.Error())
log.Error().Err(err)
return err
}
return context.JSON(200, remote)
}
func (r *RemoteApiHandler) handleGetRemotes(context echo.Context) error { func (r *RemoteApiHandler) handleGetRemotes(context echo.Context) error {
remotes, err := r.remoteManager.GetRemotes() remotes, err := r.remoteManager.GetRemotes()
if err != nil { if err != nil {

View File

@ -36,6 +36,10 @@ function ListManager(props) {
}) })
); );
createEffect(() =>
console.log(availableItemsFuse().search(availableItemsSearchString()))
);
const selectableAvailableItems = createMemo(() => const selectableAvailableItems = createMemo(() =>
(availableItemsSearchString() (availableItemsSearchString()
? availableItemsFuse() ? availableItemsFuse()

View File

@ -1,180 +0,0 @@
import { createEffect, createMemo, mergeProps } from "solid-js";
import Remote from "../data/remote";
import Command from "../data/command";
function RemoteControl(props) {
props = mergeProps(
{
remote: new Remote(),
onCommand: () => {},
},
props
);
const BUTTON_SIZE_REGULAR = "regular";
const BUTTON_SIZE_SMALL = "small";
const BUTTON_HEIGHT = 2;
const BUTTON_WIDTH = BUTTON_HEIGHT * 1.4;
const SMALL_BUTTON_HEIGHT = 1.5;
const SMALL_BUTTON_WIDTH = SMALL_BUTTON_HEIGHT * 1.3334;
const TYPES = Command.TYPES;
const commandMap = createMemo(() =>
props.remote
.getCommands()
.reduce(
(map, command) => ({ ...map, [command.getCommandType()]: command }),
{}
)
);
let layout = [
[TYPES.POWER, null, TYPES.INPUT],
[TYPES.ONE, TYPES.TWO, TYPES.THREE],
[TYPES.FOUR, TYPES.FIVE, TYPES.SIX],
[TYPES.SEVEN, TYPES.EIGHT, TYPES.NINE],
[TYPES.VOLUME_UP, TYPES.ZERO, TYPES.CHANNEL_UP],
[TYPES.VOLUME_DOWN, TYPES.MUTE, TYPES.CHANNEL_DOWN],
[TYPES.MENU, TYPES.HOME, TYPES.SETTINGS],
[TYPES.OPTIONS, TYPES.UP, TYPES.INFO],
[TYPES.LEFT, TYPES.ENTER, TYPES.RIGHT],
[TYPES.RETURN, TYPES.DOWN, TYPES.EXIT],
[TYPES.RED, TYPES.GREEN, TYPES.YELLOW, TYPES.BLUE],
[TYPES.PLAY, TYPES.PAUSE, TYPES.STOP],
[TYPES.REWIND, null, TYPES.FORWARD],
];
function toButtonProps(type) {
let mapping = {
[TYPES.POWER]: () => ({ icon: "bi-power", text: "" }),
[TYPES.INPUT]: () => ({ icon: "bi-box-arrow-in-right", text: "" }),
[TYPES.VOLUME_UP]: () => ({ icon: "bi-volume-up", text: "" }),
[TYPES.VOLUME_DOWN]: () => ({ icon: "bi-volume-down", text: "" }),
[TYPES.MUTE]: () => ({ icon: "bi-volume-mute", text: "" }),
[TYPES.CHANNEL_UP]: () => ({ icon: "bi-chevron-up", text: "" }),
[TYPES.CHANNEL_DOWN]: () => ({ icon: "bi-chevron-down", text: "" }),
[TYPES.HOME]: () => ({ icon: "bi-house-door", text: "" }),
[TYPES.SETTINGS]: () => ({ icon: "bi-gear", text: "" }),
[TYPES.INFO]: () => ({ icon: "bi-info-circle", text: "" }),
[TYPES.UP]: () => ({ icon: "bi-arrow-up", text: "" }),
[TYPES.DOWN]: () => ({ icon: "bi-arrow-down", text: "" }),
[TYPES.LEFT]: () => ({ icon: "bi-arrow-left", text: "" }),
[TYPES.RIGHT]: () => ({ icon: "bi-arrow-right", text: "" }),
[TYPES.ENTER]: () => ({ icon: "", text: "OK" }),
[TYPES.EXIT]: () => ({ icon: "", text: "EXIT" }),
[TYPES.OPTIONS]: () => ({ icon: "bi-list-task", text: "" }),
[TYPES.RETURN]: () => ({ icon: "bi-arrow-return-left", text: "" }),
[TYPES.ONE]: () => ({ icon: "", text: "1" }),
[TYPES.TWO]: () => ({ icon: "", text: "2" }),
[TYPES.THREE]: () => ({ icon: "", text: "3" }),
[TYPES.FOUR]: () => ({ icon: "", text: "4" }),
[TYPES.FIVE]: () => ({ icon: "", text: "5" }),
[TYPES.SIX]: () => ({ icon: "", text: "6" }),
[TYPES.SEVEN]: () => ({ icon: "", text: "7" }),
[TYPES.EIGHT]: () => ({ icon: "", text: "8" }),
[TYPES.NINE]: () => ({ icon: "", text: "9" }),
[TYPES.ZERO]: () => ({ icon: "", text: "0" }),
[TYPES.MENU]: () => ({ icon: "", text: "MENU" }),
[TYPES.PLAY]: () => ({ icon: "bi-play", text: "" }),
[TYPES.PAUSE]: () => ({ icon: "bi-pause", text: "" }),
[TYPES.STOP]: () => ({ icon: "bi-stop", text: "" }),
[TYPES.REWIND]: () => ({ icon: "bi-rewind", text: "" }),
[TYPES.FORWARD]: () => ({ icon: "bi-fast-forward", text: "" }),
[TYPES.RED]: () => ({
class: "bg-danger",
buttonSize: BUTTON_SIZE_SMALL,
}),
[TYPES.GREEN]: () => ({
class: "bg-success",
buttonSize: BUTTON_SIZE_SMALL,
}),
[TYPES.YELLOW]: () => ({
class: "bg-warning",
buttonSize: BUTTON_SIZE_SMALL,
}),
[TYPES.BLUE]: () => ({
class: "bg-primary",
buttonSize: BUTTON_SIZE_SMALL,
}),
};
if (!(type in mapping)) return {};
let props = mapping[type]();
props.type = type;
props.text = props.text || "";
return props;
}
function PlaceholderButton() {
return (
<div
style={`width: ${BUTTON_WIDTH}em; height: ${BUTTON_HEIGHT}em; margin: 0.2em;`}
></div>
);
}
function RemoteButton(props) {
props = mergeProps(
{
onClick: () => {},
disabled: false,
class: "",
buttonSize: BUTTON_SIZE_REGULAR,
icon: "",
text: "",
},
props
);
let buttonWidth = BUTTON_WIDTH;
let buttonHeight = BUTTON_HEIGHT;
if (props.buttonSize === BUTTON_SIZE_SMALL) {
buttonWidth = SMALL_BUTTON_WIDTH;
buttonHeight = SMALL_BUTTON_HEIGHT;
}
let buttonFontSize = buttonHeight * 0.4;
let iconSize = buttonHeight * 0.5;
return (
<button
class={
"btn btn-dark d-flex justify-content-center align-items-center " +
props.class
}
style={`width: ${buttonWidth}em; height: ${buttonHeight}em; margin: 0.2em;`}
onClick={props.onClick}
disabled={props.disabled}
>
<i style={`font-size: ${iconSize}em;`} class={"bi " + props.icon} />
<span style={`font-size: ${buttonFontSize}em;`}>{props.text}</span>
</button>
);
}
return (
<div class="d-flex flex-column justify-content-center align-items-center p-1 bg-secondary rounded">
{layout.map((row) => (
<div class="d-flex flex-row justify-content-center align-items-center">
{row
.map(toButtonProps)
.map(({ type, class: className, buttonSize, icon, text }) =>
!type ? (
<PlaceholderButton />
) : (
<RemoteButton
class={className}
buttonSize={buttonSize}
onClick={() => props.onCommand(commandMap()[type])}
disabled={!commandMap()[type]}
icon={icon}
text={text}
/>
)
)}
</div>
))}
</div>
);
}
export default RemoteControl;

View File

@ -61,7 +61,7 @@ function Command({
function getTitle() { function getTitle() {
if (_title) return _title; if (_title) return _title;
return `${Command.getTypeString(_commandType)} (${Command.getProtocolString(_protocol)})`; return `${Command.CommandTypes[commandType]} (${Command.Protocols[protocol]})`;
} }
function setTitle(title) { function setTitle(title) {
@ -84,148 +84,73 @@ function Command({
}; };
} }
Command.PROTOCOLS = { Command.Protocols = {
SAMSUNG: "samsung", samsung: "Samsung",
NEC: "nec", nec: "NEC",
ONKYO: "onkyo", onkyo: "Onkyo",
APPLE: "apple", apple: "Apple",
DENON: "denon", denon: "Denon",
SHARP: "sharp", sharp: "Sharp",
PANASONIC: "panasonic", panasonic: "Panasonic",
KASEIKYO: "kaseikyo", kaseikyo: "Kaseikyo",
JVC: "jvc", jvc: "JVC",
LG: "lg", lg: "LG",
SONY: "sony", sony: "Sony",
RC5: "rc5", rc5: "RC5",
RC6: "rc6", rc6: "RC6",
UNIVERSAL_PULSE_DISTANCE: "universal_pulse_distance", universal_pulse_distance: "Universal Pulse Distance",
UNIVERSAL_PULSE_WIDTH: "universal_pulse_width", universal_pulse_width: "Universal Pulse Width",
UNIVERSAL_PULSE_DISTANCE_WIDTH: "universal_pulse_distance_width", universal_pulse_distance_width: "Universal Pulse Distance Width",
HASH: "hash", hash: "Hash",
PRONTO: "pronto", pronto: "Pronto",
BOSE_WAVE: "bose_wave", bose_wave: "BoseWave",
BANG_OLUFSEN: "bang_olufsen", bang_olufsen: "Bang & Olufsen",
LEGO: "lego", lego: "Lego",
FAST: "fast", fast: "FAST",
WHYNTER: "whynter", whynter: "Whynter",
MAGIQUEST: "magiquest", magiquest: "MagiQuest",
} };
Command.getProtocolString = (protocol) => { Command.CommandTypes = {
let mapping = { power: "Power",
[Command.PROTOCOLS.SAMSUNG]: "Samsung", input: "Input",
[Command.PROTOCOLS.NEC]: "NEC", one: "1",
[Command.PROTOCOLS.ONKYO]: "Onkyo", two: "2",
[Command.PROTOCOLS.APPLE]: "Apple", three: "3",
[Command.PROTOCOLS.DENON]: "Denon", four: "4",
[Command.PROTOCOLS.SHARP]: "Sharp", five: "5",
[Command.PROTOCOLS.PANASONIC]: "Panasonic", six: "6",
[Command.PROTOCOLS.KASEIKYO]: "Kaseikyo", seven: "7",
[Command.PROTOCOLS.JVC]: "JVC", eight: "8",
[Command.PROTOCOLS.LG]: "LG", nine: "9",
[Command.PROTOCOLS.SONY]: "Sony", zero: "0",
[Command.PROTOCOLS.RC5]: "RC5", volume_up: "Volume Up",
[Command.PROTOCOLS.RC6]: "RC6", volume_down: "Volume Down",
[Command.PROTOCOLS.UNIVERSAL_PULSE_DISTANCE]: "Universal Pulse Distance", mute: "Mute",
[Command.PROTOCOLS.UNIVERSAL_PULSE_WIDTH]: "Universal Pulse Width", channel_up: "Channel Up",
[Command.PROTOCOLS.UNIVERSAL_PULSE_DISTANCE_WIDTH]: "Universal Pulse Distance Width", channel_down: "Channel Down",
[Command.PROTOCOLS.HASH]: "Hash", menu: "Menu",
[Command.PROTOCOLS.PRONTO]: "Pronto", home: "Home",
[Command.PROTOCOLS.BOSE_WAVE]: "BoseWave", settings: "Settings",
[Command.PROTOCOLS.BANG_OLUFSEN]: "Bang & Olufsen", options: "Options",
[Command.PROTOCOLS.LEGO]: "Lego", up: "Up",
[Command.PROTOCOLS.FAST]: "FAST", down: "Down",
[Command.PROTOCOLS.WHYNTER]: "Whynter", left: "Left",
[Command.PROTOCOLS.MAGIQUEST]: "MagiQuest", right: "Right",
} enter: "Enter",
return mapping[protocol] info: "Info",
} return: "Return",
exit: "Exit",
Command.TYPES = { red: "Red",
POWER: "power", green: "Green",
INPUT: "input", yellow: "Yellow",
ONE: "one", blue: "Blue",
TWO: "two", rewind: "Rewind",
THREE: "three", play: "Play",
FOUR: "four", pause: "Pause",
FIVE: "five", stop: "Stop",
SIX: "six", forward: "Forward",
SEVEN: "seven", other: "Other",
EIGHT: "eight", };
NINE: "nine",
ZERO: "zero",
VOLUME_UP: "volume_up",
VOLUME_DOWN: "volume_down",
MUTE: "mute",
CHANNEL_UP: "channel_up",
CHANNEL_DOWN: "channel_down",
MENU: "menu",
HOME: "home",
SETTINGS: "settings",
OPTIONS: "options",
UP: "up",
DOWN: "down",
LEFT: "left",
RIGHT: "right",
ENTER: "enter",
INFO: "info",
RETURN: "return",
EXIT: "exit",
RED: "red",
GREEN: "green",
YELLOW: "yellow",
BLUE: "blue",
REWIND: "rewind",
PLAY: "play",
PAUSE: "pause",
STOP: "stop",
FORWARD: "forward",
OTHER: "other",
}
Command.getTypeString = (type) => {
let mapping = {
[Command.TYPES.POWER]: "Power",
[Command.TYPES.INPUT]: "Input",
[Command.TYPES.ONE]: "1",
[Command.TYPES.TWO]: "2",
[Command.TYPES.THREE]: "3",
[Command.TYPES.FOUR]: "4",
[Command.TYPES.FIVE]: "5",
[Command.TYPES.SIX]: "6",
[Command.TYPES.SEVEN]: "7",
[Command.TYPES.EIGHT]: "8",
[Command.TYPES.NINE]: "9",
[Command.TYPES.ZERO]: "0",
[Command.TYPES.VOLUME_UP]: "Volume Up",
[Command.TYPES.VOLUME_DOWN]: "Volume Down",
[Command.TYPES.MUTE]: "Mute",
[Command.TYPES.CHANNEL_UP]: "Channel Up",
[Command.TYPES.CHANNEL_DOWN]: "Channel Down",
[Command.TYPES.MENU]: "Menu",
[Command.TYPES.HOME]: "Home",
[Command.TYPES.SETTINGS]: "Settings",
[Command.TYPES.OPTIONS]: "Options",
[Command.TYPES.UP]: "Up",
[Command.TYPES.DOWN]: "Down",
[Command.TYPES.LEFT]: "Left",
[Command.TYPES.RIGHT]: "Right",
[Command.TYPES.ENTER]: "Enter",
[Command.TYPES.INFO]: "Info",
[Command.TYPES.RETURN]: "Return",
[Command.TYPES.EXIT]: "Exit",
[Command.TYPES.RED]: "Red",
[Command.TYPES.GREEN]: "Green",
[Command.TYPES.YELLOW]: "Yellow",
[Command.TYPES.BLUE]: "Blue",
[Command.TYPES.REWIND]: "Rewind",
[Command.TYPES.PLAY]: "Play",
[Command.TYPES.PAUSE]: "Pause",
[Command.TYPES.STOP]: "Stop",
[Command.TYPES.FORWARD]: "Forward",
[Command.TYPES.OTHER]: "Other",
}
return mapping[type]
}
export default Command; export default Command;

View File

@ -1,4 +1,4 @@
function Remote({ id, title, commands = [] } = {}) { function Remote({ id, title, commands } = {}) {
let _id = id; let _id = id;
let _title = title; let _title = title;
let _commands = commands; let _commands = commands;

View File

@ -68,11 +68,7 @@ const Serializer = (function () {
} }
function deserializeRemote(object) { function deserializeRemote(object) {
return new Remote({ return new Remote(object);
id: object.id,
title: object.title,
commands: deserializeCommands(object.commands),
});
} }
function deserializeRemotes(objects) { function deserializeRemotes(objects) {

View File

@ -92,10 +92,8 @@ function CreateCommandModal(props) {
<option value="" selected> <option value="" selected>
Please select Please select
</option> </option>
{Object.values(Command.PROTOCOLS).map((protocol) => ( {Object.keys(Command.Protocols).map((protocol) => (
<option value={protocol}> <option value={protocol}>{Command.Protocols[protocol]}</option>
{Command.getProtocolString(protocol)}
</option>
))} ))}
</select> </select>
</div> </div>
@ -141,9 +139,9 @@ function CreateCommandModal(props) {
<option value="" selected> <option value="" selected>
Please select Please select
</option> </option>
{Object.values(Command.TYPES).map((commandType) => ( {Object.keys(Command.CommandTypes).map((commandType) => (
<option value={commandType}> <option value={commandType}>
{Command.getTypeString(commandType)} {Command.CommandTypes[commandType]}
</option> </option>
))} ))}
</select> </select>

View File

@ -35,14 +35,8 @@ FieldTitles[COMMAND_TYPE_FIELD] = "Type";
FieldTitles[TITLE_FIELD] = "Title"; FieldTitles[TITLE_FIELD] = "Title";
const CommandFieldsRequiringValueMapping = ["protocol", "commandType"]; const CommandFieldsRequiringValueMapping = ["protocol", "commandType"];
const commandTypeFuse = new Fuse( const commandTypeFuse = new Fuse(Object.values(Command.CommandTypes));
Object.values(Command.TYPES).map((type) => Command.getTypeString(type)) const protocolsFuse = new Fuse(Object.values(Command.Protocols));
);
const protocolsFuse = new Fuse(
Object.values(Command.PROTOCOLS).map((protocol) =>
Command.getProtocolString(protocol)
)
);
function ImportCommandsModal(props) { function ImportCommandsModal(props) {
const [csvString, setCsvString] = createSignal(""); const [csvString, setCsvString] = createSignal("");
@ -252,7 +246,7 @@ function ImportCommandsModal(props) {
<option value="" selected> <option value="" selected>
Please select Please select
</option> </option>
{Object.values(Command.PROTOCOLS).map( {Object.keys(Command.Protocols).map(
(protocol) => ( (protocol) => (
<option <option
value={protocol} value={protocol}
@ -262,7 +256,7 @@ function ImportCommandsModal(props) {
] === protocol ] === protocol
} }
> >
{Command.getProtocolString(protocol)} {Command.Protocols[protocol]}
</option> </option>
) )
)} )}
@ -278,7 +272,7 @@ function ImportCommandsModal(props) {
<option value="" selected> <option value="" selected>
Please select Please select
</option> </option>
{Object.values(Command.TYPES).map( {Object.keys(Command.CommandTypes).map(
(commandType) => ( (commandType) => (
<option <option
value={commandType} value={commandType}
@ -288,7 +282,7 @@ function ImportCommandsModal(props) {
] === commandType ] === commandType
} }
> >
{Command.getTypeString(commandType)} {Command.CommandTypes[commandType]}
</option> </option>
) )
)} )}
@ -324,8 +318,8 @@ function ImportCommandsModal(props) {
if (!valueMapping[PROTOCOL_FIELD][protocolValue]) { if (!valueMapping[PROTOCOL_FIELD][protocolValue]) {
let result = protocolsFuse.search(protocolValue).shift(); let result = protocolsFuse.search(protocolValue).shift();
if (result) { if (result) {
let protocol = Object.values(Command.PROTOCOLS).find( let protocol = Object.keys(Command.Protocols).find(
(protocol) => Command.getProtocolString(protocol) === result.item (protocol) => Command.Protocols[protocol] === result.item
); );
valueMapping[PROTOCOL_FIELD][protocolValue] = protocol; valueMapping[PROTOCOL_FIELD][protocolValue] = protocol;
} }
@ -333,9 +327,8 @@ function ImportCommandsModal(props) {
if (!valueMapping[COMMAND_TYPE_FIELD][commandTypeValue]) { if (!valueMapping[COMMAND_TYPE_FIELD][commandTypeValue]) {
let result = commandTypeFuse.search(commandTypeValue).shift(); let result = commandTypeFuse.search(commandTypeValue).shift();
if (result) { if (result) {
let commandType = Object.values(Command.TYPES).find( let commandType = Object.keys(Command.CommandTypes).find(
(commandType) => (commandType) => Command.CommandTypes[commandType] === result.item
Command.getTypeString(commandType) === result.item
); );
valueMapping[COMMAND_TYPE_FIELD][commandTypeValue] = commandType; valueMapping[COMMAND_TYPE_FIELD][commandTypeValue] = commandType;
} }

View File

@ -1,25 +1,9 @@
import Command from "../data/command"; import Command from "../data/command";
import Serializer from "../data/serializer"; import Serializer from "../data/serializer";
import Net from "../tools/net"; import Net from "../tools/net";
import WebRTCService from "./webrtc-service";
const MESSAGE_TYPE_COMMAND = "command";
function RemoteService() { function RemoteService() {
async function getRemote(remoteId) { let remotes = [];
let response = await Net.sendRequest({
method: "GET",
url: "/api/remotes/" + remoteId,
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
let remoteObject = JSON.parse(response.data);
return Serializer.deserializeRemote(remoteObject);
}
async function getRemotes() { async function getRemotes() {
let response = await Net.sendRequest({ let response = await Net.sendRequest({
@ -107,13 +91,7 @@ function RemoteService() {
} }
} }
function sendCommand(command) {
let commandObject = Serializer.serializeCommand(command);
WebRTCService.sendDataJson({type: MESSAGE_TYPE_COMMAND, data: commandObject});
}
return { return {
getRemote,
getRemotes, getRemotes,
createRemote, createRemote,
deleteRemote, deleteRemote,
@ -121,7 +99,6 @@ function RemoteService() {
createCommand, createCommand,
createCommands, createCommands,
deleteCommand, deleteCommand,
sendCommand,
}; };
} }

View File

@ -8,12 +8,10 @@ function WebRTCService() {
const STATE_CLOSED = "closed"; const STATE_CLOSED = "closed";
const STATE_FAILED = "failed"; const STATE_FAILED = "failed";
const ICE_CONNECTION_STATE_CHANGE_EVENT = "iceconnectionstatechange"; const ICE_CONNECTION_STATE_CHANGE_EVENT = "iceconnectionstatechange";
const DATA_CHANNEL_OPEN_EVENT = "datachannelopen";
let videoElement; let videoElement;
let peerConnection; let peerConnection;
let peerId; let peerId;
let dataChannel;
let eventEmitter = new EventEmitter(); let eventEmitter = new EventEmitter();
@ -59,19 +57,12 @@ function WebRTCService() {
console.log("Negotiation needed"); console.log("Negotiation needed");
negotiate(targetId); negotiate(targetId);
}; };
dataChannel = peerConnection.createDataChannel("data");
dataChannel.addEventListener("open", () => {
eventEmitter.dispatchEvent(DATA_CHANNEL_OPEN_EVENT);
});
negotiate(targetId); negotiate(targetId);
} }
function disconnect() { function disconnect() {
peerConnection.close(); peerConnection.close();
eventEmitter.dispatchEvent( eventEmitter.dispatchEvent(ICE_CONNECTION_STATE_CHANGE_EVENT, peerConnection.iceConnectionState);
ICE_CONNECTION_STATE_CHANGE_EVENT,
peerConnection.iceConnectionState
);
} }
async function negotiate(targetId) { async function negotiate(targetId) {
@ -134,27 +125,12 @@ function WebRTCService() {
peerConnection.addIceCandidate(iceCandidate); peerConnection.addIceCandidate(iceCandidate);
} }
function sendDataString(data) {
if (!dataChannel) return;
if (dataChannel.readyState !== "open") return;
dataChannel.send(data);
}
function sendDataJson(data) {
let dataJson = JSON.stringify(data);
sendDataString(dataJson);
}
function onStateChanged(callback) { function onStateChanged(callback) {
eventEmitter.on(ICE_CONNECTION_STATE_CHANGE_EVENT, () => { eventEmitter.on(ICE_CONNECTION_STATE_CHANGE_EVENT, () => {
callback(peerConnection.iceConnectionState); callback(peerConnection.iceConnectionState);
}); });
} }
function onDataChannelOpen(callback) {
eventEmitter.on(DATA_CHANNEL_OPEN_EVENT, callback);
}
function getConfiguration() { function getConfiguration() {
return { return {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }], iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
@ -179,10 +155,7 @@ function WebRTCService() {
disconnect, disconnect,
setVideoElement, setVideoElement,
getVideoElement, getVideoElement,
sendDataString,
sendDataJson,
onStateChanged, onStateChanged,
onDataChannelOpen,
}; };
} }

View File

@ -1,16 +1,8 @@
import { import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
createEffect,
createMemo,
createResource,
createSignal,
onCleanup,
} from "solid-js";
import WebRTCService from "../../services/webrtc-service"; import WebRTCService from "../../services/webrtc-service";
import Integration from "../../data/integration"; import Integration from "../../data/integration";
import DeviceService from "../../services/device-service"; import DeviceService from "../../services/device-service";
import UrlUtils from "../../tools/url-utils"; import UrlUtils from "../../tools/url-utils";
import RemoteControl from "../../components/remote-control";
import RemoteService from "../../services/remotes-service";
const [integration, setIntegration] = createSignal(new Integration()); const [integration, setIntegration] = createSignal(new Integration());
@ -30,16 +22,8 @@ function IntegrationView(props) {
connectionState() === WebRTCService.STATE_CLOSED connectionState() === WebRTCService.STATE_CLOSED
); );
WebRTCService.onStateChanged(handleConnectionStateChanged); WebRTCService.onStateChanged(handleConnectionStateChanged);
WebRTCService.onDataChannelOpen(handleDataChannelOpen);
let videoElement = null; let videoElement = null;
const [availableRemotes] = createResource(RemoteService.getRemotes, {
initialValue: [],
});
const [selectedRemote, setSelectedRemote] = createSignal();
createEffect(() => handleRemoteSelected(availableRemotes().shift()?.getId()));
createEffect(() => { createEffect(() => {
let url = UrlUtils.getUrl(); let url = UrlUtils.getUrl();
url = UrlUtils.addQueryParameter(url, "id", integration()?.getId()); url = UrlUtils.addQueryParameter(url, "id", integration()?.getId());
@ -56,7 +40,7 @@ function IntegrationView(props) {
async function setIntegrationFromUrl() { async function setIntegrationFromUrl() {
let integrationId = UrlUtils.getQueryParameter("id"); let integrationId = UrlUtils.getQueryParameter("id");
if (!integrationId) return; if (!integrationId) return;
let integration = await DeviceService.getIntegration(integrationId); let integration = await DeviceService.getIntegration(integrationId)
setIntegration(integration); setIntegration(integration);
} }
@ -74,22 +58,6 @@ function IntegrationView(props) {
setConnectionState(state); setConnectionState(state);
} }
function handleDataChannelOpen() {
setInterval(() => {
WebRTCService.sendDataJson({ message: "ping" });
}, 1000);
}
async function handleRemoteSelected(remoteId) {
if (!remoteId) return;
let remote = await RemoteService.getRemote(remoteId);
setSelectedRemote(remote);
}
function handleRemoteButtonPressed(command) {
RemoteService.sendCommand(command);
}
return ( return (
<div <div
class={ class={
@ -124,31 +92,13 @@ function IntegrationView(props) {
</Show> </Show>
</div> </div>
</div> </div>
<div class="flex-fill d-flex flex-row overflow-hidden"> <div class="flex-fill d-flex flex-column justify-content-center align-items-center rounded overflow-hidden">
<div class="flex-fill rounded overflow-hidden">
<video <video
ref={videoElement} ref={videoElement}
class="w-100 h-100" class="w-100 h-100"
style="background-color: #000" style="background-color: #000"
></video> ></video>
</div> </div>
<div class="d-flex flex-column ps-3">
<select
class="form-select mb-3"
onChange={(e) => handleRemoteSelected(e.target.value)}
>
{availableRemotes().map((remote, index) => (
<option value={remote.getId()} selected={index === 0}>
{remote.getTitle()}
</option>
))}
</select>
<RemoteControl
onCommand={handleRemoteButtonPressed}
remote={selectedRemote()}
/>
</div>
</div>
</div> </div>
); );
} }