Add basis of webstudio selector server UI.

This commit is contained in:
Matthew Stratford 2020-04-11 22:39:27 +01:00
parent 964977bfdf
commit 8c7df0f5ab
8 changed files with 172 additions and 10 deletions

1
.env
View file

@ -2,4 +2,5 @@ HOST=local-development.ury.org.uk
REACT_APP_VERSION=$npm_package_version REACT_APP_VERSION=$npm_package_version
REACT_APP_MYRADIO_NONAPI_BASE=https://ury.org.uk/myradio-staging REACT_APP_MYRADIO_NONAPI_BASE=https://ury.org.uk/myradio-staging
REACT_APP_MYRADIO_BASE=https://ury.org.uk/api-staging/v2 REACT_APP_MYRADIO_BASE=https://ury.org.uk/api-staging/v2
REACT_APP_BROADCAST_API_BASE=https://ury.org.uk/webstudio/api/v1
REACT_APP_WS_URL=wss://audio.ury.org.uk/webstudio/stream REACT_APP_WS_URL=wss://audio.ury.org.uk/webstudio/stream

View file

@ -1,4 +1,5 @@
REACT_APP_VERSION=$npm_package_version REACT_APP_VERSION=$npm_package_version
REACT_APP_MYRADIO_NONAPI_BASE=https://ury.org.uk/myradio REACT_APP_MYRADIO_NONAPI_BASE=https://ury.org.uk/myradio
REACT_APP_MYRADIO_BASE=https://ury.org.uk/api/v2 REACT_APP_MYRADIO_BASE=https://ury.org.uk/api/v2
REACT_APP_BROADCAST_API_BASE=https://ury.org.uk/webstudio/api/v1
REACT_APP_WS_URL=wss://audio.ury.org.uk/webstudio/stream REACT_APP_WS_URL=wss://audio.ury.org.uk/webstudio/stream

View file

@ -76,6 +76,7 @@
"react-dnd": "^9.4.0", "react-dnd": "^9.4.0",
"react-dnd-html5-backend": "^9.4.0", "react-dnd-html5-backend": "^9.4.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-live-clock": "^4.0.5",
"react-modal": "^3.11.2", "react-modal": "^3.11.2",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
"reactstrap": "^8.4.1", "reactstrap": "^8.4.1",

View file

@ -21,6 +21,20 @@
color: #09d3ac; color: #09d3ac;
} }
#timelord {
background: black;
border:red 1px solid;
padding:0;
margin:0;
color: white;
width: 300px;
max-width: 40vw;
}
#timelord .time {
font-weight: bold;
font-size: 1.1em;
}
.sp-container { .sp-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -4,19 +4,22 @@ export const MYRADIO_NON_API_BASE =
process.env.REACT_APP_MYRADIO_NONAPI_BASE!; process.env.REACT_APP_MYRADIO_NONAPI_BASE!;
export const MYRADIO_BASE_URL = export const MYRADIO_BASE_URL =
process.env.REACT_APP_MYRADIO_BASE!; process.env.REACT_APP_MYRADIO_BASE!;
export const BROADCAST_API_BASE_URL =
process.env.REACT_APP_BROADCAST_API_BASE!;
const MYRADIO_API_KEY = process.env.REACT_APP_MYRADIO_KEY!; const MYRADIO_API_KEY = process.env.REACT_APP_MYRADIO_KEY!;
class ApiException extends Error {} class ApiException extends Error {}
export async function myradioRequest( export async function apiRequest(
url: string, url: string,
method: "GET" | "POST" | "PUT", method: "GET" | "POST" | "PUT",
params: any params: any,
need_auth: boolean = true
): Promise<Response> { ): Promise<Response> {
let req = null; let req = null;
if (method === "GET") { if (method === "GET") {
req = fetch(url + qs.stringify(params, { addQueryPrefix: true }), { req = fetch(url + qs.stringify(params, { addQueryPrefix: true }), {
credentials: "include" credentials: (need_auth ? "include" : "omit")
}); });
} else { } else {
const body = JSON.stringify(params); const body = JSON.stringify(params);
@ -27,7 +30,7 @@ export async function myradioRequest(
headers: { headers: {
"Content-Type": "application/json; charset=UTF-8" "Content-Type": "application/json; charset=UTF-8"
}, },
credentials: "include" credentials: (need_auth ? "include" : "omit")
}); });
} }
return await req; return await req;
@ -38,7 +41,22 @@ export async function myradioApiRequest(
method: "GET" | "POST" | "PUT", method: "GET" | "POST" | "PUT",
params: any params: any
): Promise<any> { ): Promise<any> {
const res = await myradioRequest(MYRADIO_BASE_URL + endpoint, method, params); const res = await apiRequest(MYRADIO_BASE_URL + endpoint, method, params);
const json = await res.json();
if (json.status === "OK") {
return json.payload;
} else {
console.error(json.payload);
throw new ApiException("Request failed!");
}
}
export async function broadcastApiRequest(
endpoint: string,
method: "GET" | "POST" | "PUT",
params: any
): Promise<any> {
const res = await apiRequest(BROADCAST_API_BASE_URL + endpoint, method, params, false);
const json = await res.json(); const json = await res.json();
if (json.status === "OK") { if (json.status === "OK") {
return json.payload; return json.payload;
@ -183,7 +201,7 @@ export function getAuxPlaylists(): Promise<Array<ManagedPlaylist>> {
} }
export function loadAuxLibrary(libraryId: string): Promise<AuxItem[]> { export function loadAuxLibrary(libraryId: string): Promise<AuxItem[]> {
return myradioRequest(MYRADIO_NON_API_BASE + "/NIPSWeb/load_aux_lib", "GET", { return apiRequest(MYRADIO_NON_API_BASE + "/NIPSWeb/load_aux_lib", "GET", {
libraryid: libraryId libraryid: libraryId
}).then(res => res.json()); }).then(res => res.json());
} }

View file

@ -1,6 +1,6 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AppThunk } from "../store"; import { AppThunk } from "../store";
import { myradioApiRequest } from "../api"; import { myradioApiRequest, broadcastApiRequest } from "../api";
import { WebRTCStreamer } from "./rtc_streamer"; import { WebRTCStreamer } from "./rtc_streamer";
import * as MixerState from "../mixer/state"; import * as MixerState from "../mixer/state";
import * as NavbarState from "../navbar/state"; import * as NavbarState from "../navbar/state";
@ -9,15 +9,31 @@ import { RecordingStreamer } from "./recording_streamer";
export let streamer: WebRTCStreamer | null = null; export let streamer: WebRTCStreamer | null = null;
export type BroadcastStageEnum =
| "NOT_REGISTERED"
| "REGISTERED"
| "FAILED_REGISTRATION";
interface BroadcastState { interface BroadcastState {
stage: BroadcastStageEnum;
connID: number | null;
tracklisting: boolean; tracklisting: boolean;
connectionState: ConnectionStateEnum; connectionState: ConnectionStateEnum;
recordingState: ConnectionStateEnum; recordingState: ConnectionStateEnum;
} }
/* Overall states:
hasn't registered
registered
on air
*/
const broadcastState = createSlice({ const broadcastState = createSlice({
name: "Broadcast", name: "Broadcast",
initialState: { initialState: {
stage: "NOT_REGISTERED",
connID: null,
tracklisting: false, tracklisting: false,
connectionState: "NOT_CONNECTED", connectionState: "NOT_CONNECTED",
recordingState: "NOT_CONNECTED" recordingState: "NOT_CONNECTED"
@ -26,6 +42,14 @@ const broadcastState = createSlice({
toggleTracklisting(state) { toggleTracklisting(state) {
state.tracklisting = !state.tracklisting; state.tracklisting = !state.tracklisting;
}, },
setConnID(state, action: PayloadAction<number | null>) {
state.connID = action.payload;
if (action.payload != null) {
state.stage = "REGISTERED"
} else {
state.stage = "NOT_REGISTERED"
}
},
setConnectionState(state, action: PayloadAction<ConnectionStateEnum>) { setConnectionState(state, action: PayloadAction<ConnectionStateEnum>) {
state.connectionState = action.payload; state.connectionState = action.payload;
}, },
@ -41,6 +65,60 @@ export interface TrackListItem {
audiologid: number; audiologid: number;
} }
export const registerTimeslot = (): AppThunk => async (dispatch, getState) => {
if (getState().broadcast.stage === "NOT_REGISTERED") {
var state = getState().session;
const memberid = state.currentUser?.memberid;
const timeslotid = state.currentTimeslot?.timeslot_id;
console.log("Attempting to Register for Broadcast.");
var sourceid = 4; // TODO: make UI for this.
var connID = (await sendBroadcastRegister(timeslotid, memberid, sourceid));
if (connID !== undefined) {
dispatch(broadcastState.actions.setConnID(connID["connid"]));
dispatch(startStreaming());
}
}
};
export const cancelTimeslot = (): AppThunk => async (dispatch, getState) => {
if (getState().broadcast.stage === "REGISTERED") {
console.log("Attempting to Cancel Broadcast.");
var response = (await sendBroadcastCancel(getState().broadcast.connID));
dispatch(stopStreaming());
if (response != null) {
dispatch(broadcastState.actions.setConnID(null));
}
}
};
export function sendBroadcastRegister(timeslotid: number | undefined, memberid: number | undefined, sourceid: number): Promise<any> {
return broadcastApiRequest("/registerTimeslot", "POST", {
memberid: memberid,
timeslotid: timeslotid,
sourceid: sourceid
});
}
export function sendBroadcastCancel(connid: number | null): Promise<string | null> {
return broadcastApiRequest("/cancelTimeslot", "POST", {
connid: connid
});
}
export const { toggleTracklisting } = broadcastState.actions; export const { toggleTracklisting } = broadcastState.actions;
export const tracklistStart = ( export const tracklistStart = (

View file

@ -1,5 +1,7 @@
import React, { useRef, useEffect } from "react"; import React, { useRef, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import Clock from 'react-live-clock';
import { RootState } from "../rootReducer"; import { RootState } from "../rootReducer";
import * as BroadcastState from "../broadcast/state"; import * as BroadcastState from "../broadcast/state";
@ -16,7 +18,7 @@ export function NavBar() {
const redirect_url = encodeURIComponent(window.location.toString()); const redirect_url = encodeURIComponent(window.location.toString());
return ( return (
<> <>
<div className="navbar-nav"> <div className="navbar-nav navbar-">
<a className="navbar-brand" href="/"> <a className="navbar-brand" href="/">
<img <img
src="//ury.org.uk/myradio/img/URY.svg" src="//ury.org.uk/myradio/img/URY.svg"
@ -28,6 +30,26 @@ export function NavBar() {
<a className="navbar-brand" href="/"> <a className="navbar-brand" href="/">
<img src={appLogo} height="28" alt="Web Studio Logo" /> <img src={appLogo} height="28" alt="Web Studio Logo" />
</a> </a>
<div id="timelord" onClick={() => {
switch (broadcastState.stage) {
case "NOT_REGISTERED":
dispatch(
BroadcastState.registerTimeslot()
)
break;
case "REGISTERED":
dispatch(
BroadcastState.cancelTimeslot()
)
break;
}
}}>
<div className="time"><Clock format={'HH:mm:ss'} ticking={true} timezone={'EU/London'} /></div>
<div className="message">
{broadcastState.stage === "NOT_REGISTERED" && "Register for show"}
{broadcastState.stage === "REGISTERED" && "Cancel show"}
</div>
</div>
</div> </div>
<ul className="nav navbar-nav navbar-right"> <ul className="nav navbar-nav navbar-right">
@ -193,7 +215,7 @@ export function CombinedNavAlertBar() {
<> <>
<AlertBar /> <AlertBar />
<header className="navbar navbar-ury navbar-expand-md p-0 bd-navbar"> <header className="navbar navbar-ury navbar-expand-md p-0 bd-navbar">
<nav className="container"> <nav className="container-fluid">
<button <button
className="navbar-toggler" className="navbar-toggler"
type="button" type="button"

View file

@ -6951,6 +6951,18 @@ mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
moment-timezone@0.5.27:
version "0.5.27"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.27.tgz#73adec8139b6fe30452e78f210f27b1f346b8877"
integrity sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==
dependencies:
moment ">= 2.9.0"
moment@2.24.0, "moment@>= 2.9.0":
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
move-concurrently@^1.0.1: move-concurrently@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@ -8563,7 +8575,7 @@ prompts@^2.0.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.4" sisteransi "^1.0.4"
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: prop-types@15.7.2, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -8851,6 +8863,16 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-live-clock@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/react-live-clock/-/react-live-clock-4.0.5.tgz#d53b16be62be7c4fd9b8183db31f58bffa1c9142"
integrity sha512-NsXuUAGrUPAnJJc4RWVNE63hbb9gZNO0gN0bMbI/fMT5Iq8Oc/KmnD1yV6W/7EzhlU4jmErng/tVWmp0pDxYHw==
dependencies:
moment "2.24.0"
moment-timezone "0.5.27"
prop-types "15.7.2"
react-moment "0.9.7"
react-modal@^3.11.2: react-modal@^3.11.2:
version "3.11.2" version "3.11.2"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4"
@ -8861,6 +8883,11 @@ react-modal@^3.11.2:
react-lifecycles-compat "^3.0.0" react-lifecycles-compat "^3.0.0"
warning "^4.0.3" warning "^4.0.3"
react-moment@0.9.7:
version "0.9.7"
resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.9.7.tgz#ca570466595b1aa4f7619e62da18b3bb2de8b6f3"
integrity sha512-ifzUrUGF6KRsUN2pRG5k56kO0mJBr8kRkWb0wNvtFIsBIxOuPxhUpL1YlXwpbQCbHq23hUu6A0VEk64HsFxk9g==
react-popper@^1.3.6: react-popper@^1.3.6:
version "1.3.7" version "1.3.7"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"