Mixer sync and passthrough sync.
This commit is contained in:
parent
f161b6c806
commit
2125e320e3
|
@ -41,9 +41,11 @@ import MixerPanel from './WebDJ/MixerPanel.vue';
|
|||
import MicrophonePanel from './WebDJ/MicrophonePanel.vue';
|
||||
import PlaylistPanel from './WebDJ/PlaylistPanel.vue';
|
||||
import SettingsPanel from './WebDJ/SettingsPanel.vue';
|
||||
import {useWebDjNode} from "~/components/Public/WebDJ/useWebDjNode";
|
||||
import {provide} from "vue";
|
||||
import {useProvideWebDjNode, useWebDjNode} from "~/components/Public/WebDJ/useWebDjNode";
|
||||
import {ref} from "vue";
|
||||
import {useWebcaster, webcasterProps} from "~/components/Public/WebDJ/useWebcaster";
|
||||
import {useProvideMixer} from "~/components/Public/WebDJ/useMixerValue";
|
||||
import {useProvidePassthroughSync} from "~/components/Public/WebDJ/usePassthroughSync";
|
||||
|
||||
const props = defineProps({
|
||||
...webcasterProps,
|
||||
|
@ -54,6 +56,13 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const webcaster = useWebcaster(props);
|
||||
|
||||
const node = useWebDjNode(webcaster);
|
||||
provide('node', node);
|
||||
useProvideWebDjNode(node);
|
||||
|
||||
const mixer = ref(1.0);
|
||||
useProvideMixer(mixer);
|
||||
|
||||
const passthroughSync = ref('');
|
||||
useProvidePassthroughSync(passthroughSync);
|
||||
</script>
|
||||
|
|
|
@ -83,34 +83,48 @@ import VolumeSlider from "~/components/Public/WebDJ/VolumeSlider";
|
|||
import {useDevicesList} from "@vueuse/core";
|
||||
import {ref, watch} from "vue";
|
||||
import {useWebDjTrack} from "~/components/Public/WebDJ/useWebDjTrack";
|
||||
import {usePassthroughSync} from "~/components/Public/WebDJ/usePassthroughSync";
|
||||
|
||||
const {node, source, isPlaying, trackGain, trackPassThrough, volume, prepare, stop} = useWebDjTrack();
|
||||
const {
|
||||
createMicrophoneSource,
|
||||
source,
|
||||
isPlaying,
|
||||
trackGain,
|
||||
trackPassThrough,
|
||||
volume,
|
||||
prepare,
|
||||
stop
|
||||
} = useWebDjTrack();
|
||||
|
||||
const {createMicrophoneSource} = node;
|
||||
usePassthroughSync(trackPassThrough, 'microphone');
|
||||
|
||||
const {audioInputs} = useDevicesList({
|
||||
requestPermissions: true,
|
||||
constraints: {audio: true, video: false}
|
||||
});
|
||||
|
||||
const device = ref(audioInputs.value[0]?.deviceId);
|
||||
const device = ref(null);
|
||||
watch(audioInputs, (inputs) => {
|
||||
if (device.value === null) {
|
||||
device.value = inputs[0]?.deviceId;
|
||||
}
|
||||
});
|
||||
|
||||
let destination = null;
|
||||
|
||||
const createSource = () => {
|
||||
if (source.value != null && destination !== null) {
|
||||
if (source.value != null) {
|
||||
source.value.disconnect(destination);
|
||||
}
|
||||
|
||||
createMicrophoneSource(device.value, (newSource) => {
|
||||
source.value = newSource;
|
||||
if (destination !== null) {
|
||||
newSource.connect(destination);
|
||||
}
|
||||
newSource.connect(destination);
|
||||
});
|
||||
};
|
||||
|
||||
watch(device, () => {
|
||||
if (source.value == null) {
|
||||
if (source.value === null || destination === null) {
|
||||
return;
|
||||
}
|
||||
createSource();
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
</div>
|
||||
<div class="flex-fill px-2">
|
||||
<input
|
||||
v-model="mixerValue"
|
||||
v-model.number="mixer"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
max="2"
|
||||
step="0.05"
|
||||
class="custom-range slider"
|
||||
style="width: 200px; height: 10px;"
|
||||
@click.right.prevent="mixerValue = 0.5"
|
||||
@click.right.prevent="mixer = 1.0"
|
||||
>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
|
@ -35,7 +35,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useMixerValue} from "~/components/Public/WebDJ/useMixerValue";
|
||||
import {useInjectMixer} from "~/components/Public/WebDJ/useMixerValue";
|
||||
|
||||
const mixerValue = useMixerValue();
|
||||
const mixer = useInjectMixer();
|
||||
</script>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h5>
|
||||
</div>
|
||||
<div class="flex-shrink-0 pl-3">
|
||||
<volume-slider v-model.number="trackGain" />
|
||||
<volume-slider v-model.number="localGain" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -169,10 +169,12 @@
|
|||
import Icon from '~/components/Common/Icon';
|
||||
import VolumeSlider from "~/components/Public/WebDJ/VolumeSlider";
|
||||
import formatTime from "~/functions/formatTime";
|
||||
import {computed, ref} from "vue";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {useWebDjTrack} from "~/components/Public/WebDJ/useWebDjTrack";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {forEach} from "lodash";
|
||||
import {useInjectMixer} from "~/components/Public/WebDJ/useMixerValue";
|
||||
import {usePassthroughSync} from "~/components/Public/WebDJ/usePassthroughSync";
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
|
@ -186,7 +188,8 @@ const isLeftPlaylist = computed(() => {
|
|||
});
|
||||
|
||||
const {
|
||||
node,
|
||||
createAudioSource,
|
||||
sendMetadata,
|
||||
source,
|
||||
isPlaying,
|
||||
isPaused,
|
||||
|
@ -199,7 +202,7 @@ const {
|
|||
stop
|
||||
} = useWebDjTrack();
|
||||
|
||||
const {createFileSource, sendMetadata} = node;
|
||||
usePassthroughSync(trackPassThrough, props.id);
|
||||
|
||||
const fileIndex = ref(-1);
|
||||
const files = ref([]);
|
||||
|
@ -207,6 +210,28 @@ const duration = ref(0.0);
|
|||
const loop = ref(false);
|
||||
const playThrough = ref(false);
|
||||
|
||||
// Factor in mixer and local gain to calculate total gain.
|
||||
const localGain = ref(55);
|
||||
const mixer = useInjectMixer();
|
||||
|
||||
const computedGain = computed(() => {
|
||||
let multiplier;
|
||||
if (isLeftPlaylist.value) {
|
||||
multiplier = (mixer.value > 1)
|
||||
? 2.0 - (mixer.value)
|
||||
: 1.0;
|
||||
} else {
|
||||
multiplier = (mixer.value < 1)
|
||||
? mixer.value
|
||||
: 1.0;
|
||||
}
|
||||
|
||||
return localGain.value * multiplier;
|
||||
});
|
||||
watch(computedGain, (newGain) => {
|
||||
trackGain.value = newGain;
|
||||
}, {immediate: true});
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const langHeader = computed(() => {
|
||||
|
@ -272,7 +297,7 @@ const play = (options = {}) => {
|
|||
|
||||
let destination = prepare();
|
||||
|
||||
createFileSource(file, (newSource) => {
|
||||
createAudioSource(file, (newSource) => {
|
||||
source.value = newSource;
|
||||
newSource.connect(destination);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<small>{{ stationName }}</small>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
<div class="card-body">
|
||||
<template v-if="isConnected">
|
||||
<div class="form-group">
|
||||
<label
|
||||
|
@ -113,8 +113,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, inject, ref, watch} from "vue";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {useInjectWebDjNode} from "~/components/Public/WebDJ/useWebDjNode";
|
||||
import {usePassthroughSync} from "~/components/Public/WebDJ/usePassthroughSync";
|
||||
|
||||
const props = defineProps({
|
||||
stationName: {
|
||||
|
@ -133,7 +135,9 @@ const {
|
|||
stopStream,
|
||||
metadata,
|
||||
sendMetadata
|
||||
} = inject('node');
|
||||
} = useInjectWebDjNode();
|
||||
|
||||
usePassthroughSync(doPassThrough, 'global');
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import {useVModel} from "@vueuse/core";
|
|||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import {createGlobalState} from "@vueuse/core";
|
||||
import {ref} from "vue";
|
||||
import {inject, provide} from "vue";
|
||||
|
||||
export function useMixerValue() {
|
||||
return createGlobalState(
|
||||
() => ref(0.5)
|
||||
);
|
||||
const injectKey = "webDjMixer";
|
||||
|
||||
export function useProvideMixer(mixer) {
|
||||
provide(injectKey, mixer);
|
||||
}
|
||||
|
||||
export function useInjectMixer() {
|
||||
return inject(injectKey);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import {inject, provide, watch} from "vue";
|
||||
|
||||
const injectKey = 'webDjPassthroughSync';
|
||||
|
||||
export function useProvidePassthroughSync (passthroughSync) {
|
||||
provide(injectKey, passthroughSync);
|
||||
}
|
||||
|
||||
export function useInjectPassthroughSync() {
|
||||
return inject(injectKey);
|
||||
}
|
||||
|
||||
export function usePassthroughSync(thisPassthrough, stringVal) {
|
||||
const passthroughSync = useInjectPassthroughSync();
|
||||
|
||||
watch(passthroughSync, (newVal) => {
|
||||
if (newVal !== stringVal) {
|
||||
thisPassthrough.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
watch(thisPassthrough, (newVal) => {
|
||||
if (newVal) {
|
||||
passthroughSync.value = stringVal;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,5 +1,14 @@
|
|||
import {ref} from "vue";
|
||||
import {useUserMedia} from "@vueuse/core";
|
||||
import {inject, provide, ref} from "vue";
|
||||
|
||||
const injectKey = "webDjNode";
|
||||
|
||||
export function useInjectWebDjNode() {
|
||||
return inject(injectKey);
|
||||
}
|
||||
|
||||
export function useProvideWebDjNode(node) {
|
||||
provide(injectKey, node);
|
||||
}
|
||||
|
||||
export function useWebDjNode(webcaster) {
|
||||
const {isConnected, connect: connectSocket, metadata, sendMetadata} = webcaster;
|
||||
|
@ -103,23 +112,23 @@ export function useWebDjNode(webcaster) {
|
|||
});
|
||||
};
|
||||
|
||||
const createFileSource = (file, cb, onEnd) => {
|
||||
return createAudioSource(file, cb, onEnd);
|
||||
};
|
||||
|
||||
const createMicrophoneSource = (audioDeviceId, cb) => {
|
||||
const {stream} = useUserMedia({
|
||||
audioDeviceId: audioDeviceId,
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
video: false,
|
||||
audio: {
|
||||
deviceId: audioDeviceId
|
||||
}
|
||||
}).then((stream) => {
|
||||
let source = context.createMediaStreamSource(stream);
|
||||
source.stop = () => {
|
||||
let ref = stream.getAudioTracks();
|
||||
return (ref !== null)
|
||||
? ref[0].stop()
|
||||
: 0;
|
||||
}
|
||||
|
||||
return cb(source);
|
||||
});
|
||||
|
||||
stream.stop = () => {
|
||||
let ref = stream.getAudioTracks();
|
||||
return (ref !== null)
|
||||
? ref[0].stop()
|
||||
: 0;
|
||||
}
|
||||
|
||||
return cb(stream);
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -132,7 +141,6 @@ export function useWebDjNode(webcaster) {
|
|||
startStream,
|
||||
stopStream,
|
||||
createAudioSource,
|
||||
createFileSource,
|
||||
createMicrophoneSource,
|
||||
metadata,
|
||||
sendMetadata
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import {computed, inject, ref, shallowRef, watch} from "vue";
|
||||
import {computed, ref, shallowRef, watch} from "vue";
|
||||
import {useInjectWebDjNode} from "~/components/Public/WebDJ/useWebDjNode";
|
||||
|
||||
export function useWebDjTrack() {
|
||||
const node = inject('node');
|
||||
const {
|
||||
context,
|
||||
sink,
|
||||
createMicrophoneSource,
|
||||
createAudioSource,
|
||||
sendMetadata
|
||||
} = useInjectWebDjNode();
|
||||
|
||||
const trackGain = ref(55);
|
||||
const trackPassThrough = ref(false);
|
||||
|
@ -15,7 +22,7 @@ export function useWebDjTrack() {
|
|||
const bufferLog = Math.log(parseFloat(bufferSize));
|
||||
const log10 = 2.0 * Math.log(10);
|
||||
|
||||
let newSource = node.context.createScriptProcessor(bufferSize, 2, 2);
|
||||
let newSource = context.createScriptProcessor(bufferSize, 2, 2);
|
||||
|
||||
newSource.onaudioprocess = (buf) => {
|
||||
position.value = source.value?.position();
|
||||
|
@ -38,7 +45,7 @@ export function useWebDjTrack() {
|
|||
};
|
||||
|
||||
const createPassThrough = () => {
|
||||
let newSource = node.context.createScriptProcessor(256, 2, 2);
|
||||
let newSource = context.createScriptProcessor(256, 2, 2);
|
||||
|
||||
newSource.onaudioprocess = (buf) => {
|
||||
for (let channel = 0; channel < buf.inputBuffer.numberOfChannels; channel++) {
|
||||
|
@ -70,16 +77,16 @@ export function useWebDjTrack() {
|
|||
|
||||
const prepare = () => {
|
||||
controlsNode = createControlsNode();
|
||||
controlsNode.connect(node.sink);
|
||||
controlsNode.connect(sink);
|
||||
|
||||
trackGainNode = node.context.createGain();
|
||||
trackGainNode = context.createGain();
|
||||
trackGainNode.connect(controlsNode);
|
||||
|
||||
passThroughNode = createPassThrough();
|
||||
passThroughNode.connect(node.context.destination);
|
||||
passThroughNode.connect(context.destination);
|
||||
trackGainNode.connect(passThroughNode);
|
||||
|
||||
node.context.resume();
|
||||
context.resume();
|
||||
|
||||
return trackGainNode;
|
||||
}
|
||||
|
@ -120,7 +127,9 @@ export function useWebDjTrack() {
|
|||
};
|
||||
|
||||
return {
|
||||
node,
|
||||
createMicrophoneSource,
|
||||
createAudioSource,
|
||||
sendMetadata,
|
||||
source,
|
||||
trackGain,
|
||||
trackPassThrough,
|
||||
|
|
|
@ -53,15 +53,20 @@ export function useWebcaster(props) {
|
|||
|
||||
isConnected.value = true;
|
||||
|
||||
// Timeout as Liquidsoap won't return any success/failure message, so the only
|
||||
// way we know if we're still connected is to set a timer.
|
||||
setTimeout(() => {
|
||||
if (isConnected.value) {
|
||||
notifySuccess($gettext('WebDJ connected!'));
|
||||
|
||||
if (metadata.value !== null) {
|
||||
sendMetadata(metadata.value);
|
||||
socket.send(JSON.stringify({
|
||||
type: "metadata",
|
||||
data: metadata.value
|
||||
}));
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
socket.onerror = () => {
|
||||
|
@ -74,13 +79,13 @@ export function useWebcaster(props) {
|
|||
|
||||
mediaRecorder.ondataavailable = async (e) => {
|
||||
const data = await e.data.arrayBuffer();
|
||||
if (isConnected()) {
|
||||
if (isConnected.value) {
|
||||
socket.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
if (isConnected()) {
|
||||
if (isConnected.value) {
|
||||
socket.close();
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue