Add stream notifications, clean up connected status.

This commit is contained in:
Buster Neece 2022-12-31 19:56:25 -06:00
parent 901faaee50
commit f161b6c806
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
3 changed files with 137 additions and 142 deletions

View File

@ -8,125 +8,94 @@
</h5> </h5>
</div> </div>
<div class="card-body pt-0"> <div class="card-body pt-0">
<div class="form-row pb-4"> <template v-if="isConnected">
<div class="col-sm-12"> <div class="form-group">
<ul class="nav nav-tabs card-header-tabs mt-0"> <label
<li class="nav-item"> for="metadata_title"
<a class="mb-2"
class="nav-link active" >
href="#settings" {{ $gettext('Title') }}
data-toggle="tab" </label>
> <div class="controls">
{{ $gettext('Settings') }} <input
</a> id="metadata_title"
</li> v-model="shownMetadata.title"
<li class="nav-item"> class="form-control"
<a type="text"
class="nav-link"
href="#metadata"
data-toggle="tab"
>
{{ $gettext('Metadata') }}
</a>
</li>
</ul>
</div>
</div>
<div class="form-row">
<div class="col-sm-12">
<div class="tab-content mt-1">
<div
id="settings"
class="tab-pane active"
> >
<div class="form-group">
<label class="mb-2">
{{ $gettext('DJ Credentials') }}
</label>
<div class="form-row">
<div class="col-6">
<input
v-model="djUsername"
type="text"
class="form-control"
:placeholder="$gettext('Username')"
>
</div>
<div class="col-6">
<input
v-model="djPassword"
type="password"
class="form-control"
:placeholder="$gettext('Password')"
>
</div>
</div>
</div>
</div>
<div
id="metadata"
class="tab-pane"
>
<div class="form-group">
<label
for="metadata_title"
class="mb-2"
>
{{ $gettext('Title') }}
</label>
<div class="controls">
<input
id="metadata_title"
v-model="shownMetadata.title"
class="form-control"
type="text"
:disabled="!isStreaming"
>
</div>
</div>
<div class="form-group">
<label
for="metadata_artist"
class="mb-2"
>
{{ $gettext('Artist') }}
</label>
<div class="controls">
<input
id="metadata_artist"
v-model="shownMetadata.artist"
class="form-control"
type="text"
:disabled="!isStreaming"
>
</div>
</div>
<div class="form-group">
<button
class="btn btn-primary"
:disabled="!isStreaming"
@click="updateMetadata"
>
{{ $gettext('Update Metadata') }}
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> <div class="form-group">
<label
for="metadata_artist"
class="mb-2"
>
{{ $gettext('Artist') }}
</label>
<div class="controls">
<input
id="metadata_artist"
v-model="shownMetadata.artist"
class="form-control"
type="text"
>
</div>
</div>
<div class="form-group">
<button
class="btn btn-primary"
@click="updateMetadata"
>
{{ $gettext('Update Metadata') }}
</button>
</div>
</template>
<template v-else>
<div class="form-group">
<label
for="dj_username"
class="mb-2"
>
{{ $gettext('Username') }}
</label>
<div class="controls">
<input
id="dj_username"
v-model="djUsername"
type="text"
class="form-control"
>
</div>
</div>
<div class="form-group">
<label
for="dj_password"
class="mb-2"
>
{{ $gettext('Password') }}
</label>
<div class="controls">
<input
id="dj_password"
v-model="djPassword"
type="password"
class="form-control"
>
</div>
</div>
</template>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<button <button
v-if="!isStreaming" v-if="!isConnected"
class="btn btn-success" class="btn btn-success"
@click="startStream(djUsername, djPassword)" @click="startStream(djUsername, djPassword)"
> >
{{ langStreamButton }} {{ langStreamButton }}
</button> </button>
<button <button
v-if="isStreaming" v-if="isConnected"
class="btn btn-danger" class="btn btn-danger"
@click="stopStream" @click="stopStream"
> >
@ -144,8 +113,7 @@
</template> </template>
<script setup> <script setup>
import {computed, inject, ref} from "vue"; import {computed, inject, ref, watch} from "vue";
import {syncRef} from "@vueuse/core";
import {useTranslate} from "~/vendor/gettext"; import {useTranslate} from "~/vendor/gettext";
const props = defineProps({ const props = defineProps({
@ -160,7 +128,7 @@ const djPassword = ref(null);
const { const {
doPassThrough, doPassThrough,
isStreaming, isConnected,
startStream, startStream,
stopStream, stopStream,
metadata, metadata,
@ -170,13 +138,22 @@ const {
const {$gettext} = useTranslate(); const {$gettext} = useTranslate();
const langStreamButton = computed(() => { const langStreamButton = computed(() => {
return (isStreaming.value) return (isConnected.value)
? $gettext('Stop Streaming') ? $gettext('Stop Streaming')
: $gettext('Start Streaming'); : $gettext('Start Streaming');
}); });
const shownMetadata = ref({}); const shownMetadata = ref({});
syncRef(metadata, shownMetadata, {direction: "ltr"}); watch(metadata, (newMeta) => {
if (newMeta === null) {
newMeta = {
artist: '',
title: ''
};
}
shownMetadata.value = newMeta;
});
const updateMetadata = () => { const updateMetadata = () => {
sendMetadata(shownMetadata.value); sendMetadata(shownMetadata.value);

View File

@ -2,8 +2,9 @@ import {ref} from "vue";
import {useUserMedia} from "@vueuse/core"; import {useUserMedia} from "@vueuse/core";
export function useWebDjNode(webcaster) { export function useWebDjNode(webcaster) {
const {isConnected, connect: connectSocket, metadata, sendMetadata} = webcaster;
const doPassThrough = ref(false); const doPassThrough = ref(false);
const isStreaming = ref(false);
const context = new AudioContext({ const context = new AudioContext({
sampleRate: 44100 sampleRate: 44100
@ -12,7 +13,7 @@ export function useWebDjNode(webcaster) {
const sink = context.createScriptProcessor(256, 2, 2); const sink = context.createScriptProcessor(256, 2, 2);
sink.onaudioprocess = (buf) => { sink.onaudioprocess = (buf) => {
for (let channel = 0; channel < buf.inputBuffer.numberOfChannels - 1; channel++) { for (let channel = 0; channel < buf.inputBuffer.numberOfChannels; channel++) {
let channelData = buf.inputBuffer.getChannelData(channel); let channelData = buf.inputBuffer.getChannelData(channel);
buf.outputBuffer.getChannelData(channel).set(channelData); buf.outputBuffer.getChannelData(channel).set(channelData);
} }
@ -21,7 +22,7 @@ export function useWebDjNode(webcaster) {
const passThrough = context.createScriptProcessor(256, 2, 2); const passThrough = context.createScriptProcessor(256, 2, 2);
passThrough.onaudioprocess = (buf) => { passThrough.onaudioprocess = (buf) => {
for (let channel = 0; channel < buf.inputBuffer.numberOfChannels - 1; channel++) { for (let channel = 0; channel < buf.inputBuffer.numberOfChannels; channel++) {
let channelData = buf.inputBuffer.getChannelData(channel); let channelData = buf.inputBuffer.getChannelData(channel);
if (doPassThrough.value) { if (doPassThrough.value) {
@ -43,8 +44,6 @@ export function useWebDjNode(webcaster) {
let mediaRecorder; let mediaRecorder;
const startStream = (username = null, password = null) => { const startStream = (username = null, password = null) => {
isStreaming.value = true;
context.resume(); context.resume();
mediaRecorder = new MediaRecorder( mediaRecorder = new MediaRecorder(
@ -55,14 +54,13 @@ export function useWebDjNode(webcaster) {
} }
); );
webcaster.connect(mediaRecorder, username, password); connectSocket(mediaRecorder, username, password);
mediaRecorder.start(1000); mediaRecorder.start(1000);
} }
const stopStream = () => { const stopStream = () => {
mediaRecorder?.stop(); mediaRecorder?.stop();
isStreaming.value = false;
}; };
const createAudioSource = ({file, audio}, cb, onEnd) => { const createAudioSource = ({file, audio}, cb, onEnd) => {
@ -124,16 +122,9 @@ export function useWebDjNode(webcaster) {
return cb(stream); return cb(stream);
}; };
const metadata = ref({});
const sendMetadata = (data) => {
webcaster.sendMetadata(data);
metadata.value = data;
};
return { return {
doPassThrough, doPassThrough,
isStreaming, isConnected,
context, context,
sink, sink,
passThrough, passThrough,

View File

@ -1,3 +1,7 @@
import {ref, shallowRef} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useTranslate} from "~/vendor/gettext";
export const webcasterProps = { export const webcasterProps = {
baseUri: { baseUri: {
type: String, type: String,
@ -8,16 +12,27 @@ export const webcasterProps = {
export function useWebcaster(props) { export function useWebcaster(props) {
const {baseUri} = props; const {baseUri} = props;
const {notifySuccess, notifyError} = useNotify();
const {$gettext} = useTranslate();
const metadata = shallowRef(null);
const isConnected = ref(false);
let socket = null; let socket = null;
let mediaRecorder = null;
const isConnected = () => { const sendMetadata = (data) => {
return socket !== null && socket.readyState === WebSocket.OPEN; metadata.value = data;
};
const connect = (newMediaRecorder, username = null, password = null) => { if (isConnected.value) {
socket.send(JSON.stringify({
type: "metadata",
data,
}));
}
}
const connect = (mediaRecorder, username = null, password = null) => {
socket = new WebSocket(baseUri, "webcast"); socket = new WebSocket(baseUri, "webcast");
mediaRecorder = newMediaRecorder;
let hello = { let hello = {
mime: mediaRecorder.mimeType, mime: mediaRecorder.mimeType,
@ -34,7 +49,27 @@ export function useWebcaster(props) {
socket.send(JSON.stringify({ socket.send(JSON.stringify({
type: "hello", type: "hello",
data: hello data: hello
})) }));
isConnected.value = true;
setTimeout(() => {
if (isConnected.value) {
notifySuccess($gettext('WebDJ connected!'));
if (metadata.value !== null) {
sendMetadata(metadata.value);
}
}
}, 2000);
};
socket.onerror = () => {
notifyError($gettext('An error occurred with the WebDJ socket.'));
}
socket.onclose = () => {
isConnected.value = false;
}; };
mediaRecorder.ondataavailable = async (e) => { mediaRecorder.ondataavailable = async (e) => {
@ -51,18 +86,10 @@ export function useWebcaster(props) {
}; };
}; };
const sendMetadata = (data) => {
if (isConnected()) {
socket.send(JSON.stringify({
type: "metadata",
data,
}));
}
}
return { return {
isConnected, isConnected,
connect, connect,
metadata,
sendMetadata sendMetadata
} }
} }