Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recording improvements and stuff for pre-recing shows #165

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,22 @@ $number-of-channels: 3;
.hover-label {
display: none;
}

@media (max-height: 900px) {
.hover-label {
font-size: 10px;
margin-top: -5px;
display: block;
width: 100%;
}

&:hover {
height: 100%;

.hover-label-hide {
display: none;
}
}

&:not(:hover) {
overflow: hidden;
height: 15px;

> *:not(.hover-label) {
display: none !important;
}
Expand All @@ -70,7 +65,6 @@ $number-of-channels: 3;
grid-template-rows: 100%;
grid-gap: 10px;
padding: 10px;

.channel-vu {
text-align: center;
background: black;
Expand Down Expand Up @@ -141,6 +135,12 @@ $number-of-channels: 3;
box-sizing: content-box;
}

#playoutIframe {
width: 100%;
height: 100%;
box-sizing: content-box;
}

.sp-mic-live {
position: fixed;
top: 0;
Expand All @@ -161,16 +161,13 @@ $number-of-channels: 3;
width: 100%;
display: inline-block;
text-align: center;

padding: 0;

.text {
margin: 0;
float: left;
padding: 0 10px;
border-right: 3px solid white;
}

&.live {
border: 3px solid red;
.text {
Expand Down
20 changes: 12 additions & 8 deletions src/broadcast/recording_streamer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { Streamer } from "./streamer";
export class RecordingStreamer extends Streamer {
recorder: MediaRecorder;
chunks: Blob[];
url: string;
modalCallback: () => void;

constructor(stream: MediaStream) {
super();
this.recorder = new MediaRecorder(stream);
this.chunks = [];
this.url = "";
this.modalCallback = () => {};

this.recorder.ondataavailable = (e) => {
this.chunks.push(e.data);
};
Expand All @@ -17,14 +22,10 @@ export class RecordingStreamer extends Streamer {
this.recorder.onstop = () => {
this.onStateChange("NOT_CONNECTED");
const finalData = new Blob(this.chunks, {
type: "audio/ogg; codecs=opus",
type: "audio/mp3; codecs=mpeg",
});
const url = URL.createObjectURL(finalData);

const a = document.createElement("a");
a.href = url;
a.download = "recorded.ogg";
a.click();
this.url = URL.createObjectURL(finalData);
this.modalCallback();
};
this.recorder.onerror = (e) => {
console.error(e.error);
Expand All @@ -36,7 +37,10 @@ export class RecordingStreamer extends Streamer {
this.chunks = [];
this.recorder.start();
}
async stop(): Promise<void> {
async stop(callback?: () => void): Promise<void> {
if (callback) {
this.modalCallback = callback;
}
this.recorder.stop();
}
}
14 changes: 12 additions & 2 deletions src/broadcast/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,20 @@ export const startRecording = (): AppThunk => async (dispatch) => {
await recorder.start();
};

export const stopRecording = (): AppThunk => async (dispatch) => {
export const stopRecording = (callback: () => void): AppThunk => async (
dispatch
) => {
if (recorder) {
await recorder.stop();
await recorder.stop(callback);
} else {
console.warn("stopRecording called with no recorder!");
}
};

export const getRecording = () => {
if (recorder) {
return recorder.url;
}

// There isn't a console.warn here, because it spammed on load for some reason
};
10 changes: 10 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ export function secToHHMM(sec: number = 0) {
return d.toLocaleString("en-GB").split(" ")[1];
}

export function HHMMTosec(timeString: string = "00:00:00") {
var s: number = parseInt(timeString[7]);
s += 10 * parseInt(timeString[6]);
s += 60 * parseInt(timeString[4]);
s += 600 * parseInt(timeString[3]);
s += 3600 * parseInt(timeString[1]);
s += 36000 * parseInt(timeString[0]);
return s;
}

export function timestampToHHMM(sec: number = 0) {
return format(fromUnixTime(sec), "HH:mm:ss");
}
Expand Down
63 changes: 63 additions & 0 deletions src/navbar/FinishRecordingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from "react";
import { FaTimes, FaMicrophone } from "react-icons/fa";
import Modal from "react-modal";
import { Button } from "reactstrap";
import * as BroadcastState from "../broadcast/state";

interface FinishRecordingModal {
isOpen: boolean;
close: () => any;
}

export function FinishRecordingModal(props: FinishRecordingModal) {
return (
<Modal isOpen={props.isOpen} onRequestClose={props.close}>
<div>
<h1 className="d-inline">
<FaMicrophone className="mx-2" size={30} />
Recording Complete
</h1>
<Button
onClick={props.close}
className="float-right pt-2 pb-2"
color="primary"
>
<FaTimes />
</Button>
</div>
<hr />
<p>
Your recording is now complete. You can listen to it back and then
download it and upload it to your show plan if you want.
</p>

<audio controls draggable={true}>
<source src={BroadcastState.getRecording()} type="audio/mp3" />
</audio>
<div>
<Button
onClick={() => {
const a = document.createElement("a");
a.href = BroadcastState.getRecording()!;
a.download = "recorded.mp3";
a.click();
}}
className="pt-1"
color="primary"
>
Download
</Button>
</div>

<iframe
id="uploadIframe"
title="Recording Uploader"
src={
process.env.REACT_APP_MYRADIO_NONAPI_BASE + "/NIPSWeb/manage_library/"
}
frameBorder="0"
></iframe>
<div></div>
</Modal>
);
}
19 changes: 15 additions & 4 deletions src/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { closeAlert } from "./state";
import { ConnectionStateEnum } from "../broadcast/streamer";
import { VUMeter } from "../optionsMenu/helpers/VUMeter";
import { getShowplan } from "../showplanner/state";
import { FinishRecordingModal } from "./FinishRecordingModal";

import * as OptionsMenuState from "../optionsMenu/state";

Expand Down Expand Up @@ -156,6 +157,8 @@ export function NavBarMain() {

const [connectButtonAnimating, setConnectButtonAnimating] = useState(false);

const [finishRecordingModal, setFinishRecordingModal] = useState(false);

const prevRegistrationStage = useRef(broadcastState.stage);
useEffect(() => {
if (broadcastState.stage !== prevRegistrationStage.current) {
Expand All @@ -170,6 +173,12 @@ export function NavBarMain() {

return (
<>
<FinishRecordingModal
isOpen={finishRecordingModal}
close={() => {
setFinishRecordingModal(false);
}}
/>
<ul className="nav navbar-nav navbar-left">
<li
className="btn rounded-0 py-2 nav-link nav-item"
Expand Down Expand Up @@ -243,13 +252,15 @@ export function NavBarMain() {
? "btn-outline-danger active"
: "btn-outline-light")
}
onClick={() =>
onClick={() => {
dispatch(
broadcastState.recordingState === "NOT_CONNECTED"
? BroadcastState.startRecording()
: BroadcastState.stopRecording()
)
}
: BroadcastState.stopRecording(() => {
setFinishRecordingModal(true);
})
);
}}
>
<FaCircle
size={17}
Expand Down
37 changes: 37 additions & 0 deletions src/showplanner/AutoPlayoutModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { FaTimes, FaPlayCircle } from "react-icons/fa";
import Modal from "react-modal";
import { Button } from "reactstrap";

interface AutoPlayoutProps {
isOpen: boolean;
close: () => any;
}

export function AutoPlayoutModal(props: AutoPlayoutProps) {
return (
<Modal isOpen={props.isOpen} onRequestClose={props.close}>
<div>
<h1 className="d-inline">
<FaPlayCircle className="mx-2" size={30} />
URY Automatic Playout
</h1>
<Button
onClick={props.close}
className="float-right pt-1"
color="primary"
>
<FaTimes />
</Button>
</div>
<hr />
<iframe
id="playoutIframe"
src={process.env.REACT_APP_MYRADIO_NONAPI_BASE + "/NIPSWeb/playout/"}
frameBorder="0"
title="URY Automatic Playout"
></iframe>
<div></div>
</Modal>
);
}
12 changes: 11 additions & 1 deletion src/showplanner/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { omit } from "lodash";
import { RootState } from "../rootReducer";
import * as MixerState from "../mixer/state";
import * as ShowPlanState from "../showplanner/state";
import { secToHHMM, timestampToHHMM } from "../lib/utils";
import { HHMMTosec, secToHHMM, timestampToHHMM } from "../lib/utils";
import ProModeButtons from "./ProModeButtons";
import { VUMeter } from "../optionsMenu/helpers/VUMeter";
import * as api from "../api";
Expand Down Expand Up @@ -221,6 +221,15 @@ export function Player({ id }: { id: number }) {
throw new Error("Unknown Player VUMeter source: " + id);
}
};

var duration: number = 0;
const plan = useSelector((state: RootState) => state.showplan.plan);
plan?.forEach((pItem) => {
if (pItem.channel === id) {
duration += HHMMTosec(pItem.length);
}
});

return (
<div
className={
Expand Down Expand Up @@ -269,6 +278,7 @@ export function Player({ id }: { id: number }) {
<FaRedo />
&nbsp; Repeat {playerState.repeat}
</button>
<div>Total Time: {secToHHMM(duration)}</div>
</div>
{proMode && <ProModeButtons channel={id} />}
<div className="card-body p-0">
Expand Down
17 changes: 17 additions & 0 deletions src/showplanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
FaMicrophone,
FaTrash,
FaUpload,
FaPlayCircle,
} from "react-icons/fa";
import { VUMeter } from "../optionsMenu/helpers/VUMeter";
import Stopwatch from "react-stopwatch";
Expand Down Expand Up @@ -50,6 +51,7 @@ import { CombinedNavAlertBar } from "../navbar";
import { OptionsMenu } from "../optionsMenu";
import { WelcomeModal } from "./WelcomeModal";
import { PisModal } from "./PISModal";
import { AutoPlayoutModal } from "./AutoPlayoutModal";
import { LibraryUploadModal } from "./LibraryUploadModal";
import { ImporterModal } from "./ImporterModal";
import "./channel.scss";
Expand Down Expand Up @@ -88,6 +90,7 @@ function LibraryColumn() {
(state: RootState) => state.showplan
);

const [autoPlayoutModal, setAutoPlayoutModal] = useState(false);
const [showLibraryUploadModal, setShowLibraryModal] = useState(false);
const [showImporterModal, setShowImporterModal] = useState(false);

Expand All @@ -97,6 +100,10 @@ function LibraryColumn() {

return (
<>
<AutoPlayoutModal
isOpen={autoPlayoutModal}
close={() => setAutoPlayoutModal(false)}
/>
<LibraryUploadModal
isOpen={showLibraryUploadModal}
close={() => setShowLibraryModal(false)}
Expand All @@ -111,6 +118,16 @@ function LibraryColumn() {
<FaBookOpen className="mx-2" size={28} />
Libraries
</h2>
<Button
className="mr-1"
color="primary"
title="Auto Playout"
size="sm"
outline={true}
onClick={() => setAutoPlayoutModal(true)}
>
<FaPlayCircle /> Auto Playout
</Button>
<Button
className="mr-1"
color="primary"
Expand Down