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>
</div>
<div class="card-body pt-0">
<div class="form-row pb-4">
<div class="col-sm-12">
<ul class="nav nav-tabs card-header-tabs mt-0">
<li class="nav-item">
<a
class="nav-link active"
href="#settings"
data-toggle="tab"
>
{{ $gettext('Settings') }}
</a>
</li>
<li class="nav-item">
<a
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"
<template v-if="isConnected">
<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"
>
<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 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 class="card-actions">
<button
v-if="!isStreaming"
v-if="!isConnected"
class="btn btn-success"
@click="startStream(djUsername, djPassword)"
>
{{ langStreamButton }}
</button>
<button
v-if="isStreaming"
v-if="isConnected"
class="btn btn-danger"
@click="stopStream"
>
@ -144,8 +113,7 @@
</template>
<script setup>
import {computed, inject, ref} from "vue";
import {syncRef} from "@vueuse/core";
import {computed, inject, ref, watch} from "vue";
import {useTranslate} from "~/vendor/gettext";
const props = defineProps({
@ -160,7 +128,7 @@ const djPassword = ref(null);
const {
doPassThrough,
isStreaming,
isConnected,
startStream,
stopStream,
metadata,
@ -170,13 +138,22 @@ const {
const {$gettext} = useTranslate();
const langStreamButton = computed(() => {
return (isStreaming.value)
return (isConnected.value)
? $gettext('Stop Streaming')
: $gettext('Start Streaming');
});
const shownMetadata = ref({});
syncRef(metadata, shownMetadata, {direction: "ltr"});
watch(metadata, (newMeta) => {
if (newMeta === null) {
newMeta = {
artist: '',
title: ''
};
}
shownMetadata.value = newMeta;
});
const updateMetadata = () => {
sendMetadata(shownMetadata.value);

View File

@ -2,8 +2,9 @@ import {ref} from "vue";
import {useUserMedia} from "@vueuse/core";
export function useWebDjNode(webcaster) {
const {isConnected, connect: connectSocket, metadata, sendMetadata} = webcaster;
const doPassThrough = ref(false);
const isStreaming = ref(false);
const context = new AudioContext({
sampleRate: 44100
@ -12,7 +13,7 @@ export function useWebDjNode(webcaster) {
const sink = context.createScriptProcessor(256, 2, 2);
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);
buf.outputBuffer.getChannelData(channel).set(channelData);
}
@ -21,7 +22,7 @@ export function useWebDjNode(webcaster) {
const passThrough = context.createScriptProcessor(256, 2, 2);
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);
if (doPassThrough.value) {
@ -43,8 +44,6 @@ export function useWebDjNode(webcaster) {
let mediaRecorder;
const startStream = (username = null, password = null) => {
isStreaming.value = true;
context.resume();
mediaRecorder = new MediaRecorder(
@ -55,14 +54,13 @@ export function useWebDjNode(webcaster) {
}
);
webcaster.connect(mediaRecorder, username, password);
connectSocket(mediaRecorder, username, password);
mediaRecorder.start(1000);
}
const stopStream = () => {
mediaRecorder?.stop();
isStreaming.value = false;
};
const createAudioSource = ({file, audio}, cb, onEnd) => {
@ -124,16 +122,9 @@ export function useWebDjNode(webcaster) {
return cb(stream);
};
const metadata = ref({});
const sendMetadata = (data) => {
webcaster.sendMetadata(data);
metadata.value = data;
};
return {
doPassThrough,
isStreaming,
isConnected,
context,
sink,
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 = {
baseUri: {
type: String,
@ -8,16 +12,27 @@ export const webcasterProps = {
export function useWebcaster(props) {
const {baseUri} = props;
const {notifySuccess, notifyError} = useNotify();
const {$gettext} = useTranslate();
const metadata = shallowRef(null);
const isConnected = ref(false);
let socket = null;
let mediaRecorder = null;
const isConnected = () => {
return socket !== null && socket.readyState === WebSocket.OPEN;
};
const sendMetadata = (data) => {
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");
mediaRecorder = newMediaRecorder;
let hello = {
mime: mediaRecorder.mimeType,
@ -34,7 +49,27 @@ export function useWebcaster(props) {
socket.send(JSON.stringify({
type: "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) => {
@ -51,18 +86,10 @@ export function useWebcaster(props) {
};
};
const sendMetadata = (data) => {
if (isConnected()) {
socket.send(JSON.stringify({
type: "metadata",
data,
}));
}
}
return {
isConnected,
connect,
metadata,
sendMetadata
}
}