Skip to content

Commit

Permalink
feat: OBS toggle audio source (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
skarab42 authored Feb 28, 2021
1 parent c490b6b commit 4c23b01
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 34 deletions.
5 changes: 5 additions & 0 deletions app/server/libs/actions/types/obs.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ function create(action) {
}

const actions = {
ToggleAudio(action) {
const { source } = action.widget.component.props;

return obs.send("ToggleMute", { source });
},
ToggleScene(action) {
const { currentScene } = obs.getState();
const { scene1, scene2 } = action.widget.component.props;
Expand Down
32 changes: 31 additions & 1 deletion app/server/libs/obs.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ let reconnectionTimeoutId = null;
let recordingHeartbeatId = null;
let recordingHeartbeatTimeout = 2000;

let sourceTypes = [];

let state = {
connected: false,
connecting: false,
Expand Down Expand Up @@ -63,6 +65,24 @@ function updateSceneList() {
});
}

function updateSourceTypesList() {
return send("GetSourceTypesList").then(({ types }) => {
sourceTypes = types;
});
}

function updateSourcesList() {
return send("GetSourcesList").then(({ sources }) => {
sources = sources.map((source) => {
const sourceType = sourceTypes.find(
(type) => type.typeId === source.typeId
);
return sourceType ? { ...source, caps: sourceType.caps } : source;
});
updateState({ sources });
});
}

function recordingHeartbeat() {
recordingHeartbeatId = setTimeout(() => {
updateStreamStatus();
Expand Down Expand Up @@ -103,6 +123,14 @@ function registerEvents(obs) {
});

obs.on("ScenesChanged", updateSceneList);

obs.on("SourceVolumeChanged", ({ sourceName, volume }) => {
emit(`source.volume`, { sourceName, volume });
});

obs.on("SourceMuteStateChanged", ({ sourceName, muted }) => {
emit(`source.muted`, { sourceName, muted });
});
}

function onMessage(obs) {
Expand Down Expand Up @@ -157,14 +185,16 @@ function connect({ host = "localhost", port = 4444, password = null } = {}) {

obs
.connect({ address, password })
.then(() => {
.then(async () => {
logger.info("Connected");
connecting = false;
updateState({ connected: true, connecting });
obs.on("ConnectionClosed", onConnectionClosed);
watch && onMessage(obs);
registerEvents(obs);
await updateSourceTypesList();
updateStreamStatus();
updateSourcesList();
updateSceneList();
emit("connected");
})
Expand Down
1 change: 1 addition & 0 deletions app/server/libs/twitch/pushActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const types = {
SceneList: "obs",
GoToScene: "obs",
ToggleScene: "obs",
ToggleAudio: "obs",
AnimeTimeline: "anime",
};

Expand Down
19 changes: 11 additions & 8 deletions app/static/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,29 +160,32 @@
"rule": "rule",
"group": "group",
"and": "and",
"or": "or"
"or": "or",
"source": "source"
},
"obs": {
"scene-list": "OBS | Scene list",
"go-to-scene": "OBS | Go to scene",
"toggle-scene": "OBS | toggle between two scenes",
"toggle-scene": "OBS | Toggle between two scenes",
"toggle-audio": "OBS | Toggle audio source",
"no-scene-selected": "No scene selected",
"connect-at-startup": "Connect OBS at startup",
"first-start-install-sentence": "If you want to use OBS with Marv you need to install OBS WebSocket.",
"overlay-install-sentence": "To install Marv, create a browser source in OBS with the url below.",
"check-disable-source-not-visible": "Check: Shutdown source when not visible.",
"check-refresh-browser-on-activate": "Check: Refresh browser when scene becomes active.",
"overlay-not-found": "Overlay not found"
"overlay-not-found": "Overlay not found",
"no-source-selected": "obs.no-source-selected"
},
"anime": {
"timeline": "Anime | Timeline"
"timeline": "Animation"
},
"twitch": {
"chat": "Twitch chat",
"stream": "Twitch stream",
"chat": "Twitch | Chat",
"stream": "Twitch | Stream",
"rewards": "Twitch | Rewards",
"followers": "Twitch | Followers",
"commands": "Commands",
"rewards": "Rewards",
"followers": "Followers",
"connect-at-startup": "Connect Twitch at startup",
"empty-command-list": "Empty command list",
"command-cooldown": "Cooldown \"{{command}}\" -> ~{{rest}}",
Expand Down
19 changes: 11 additions & 8 deletions app/static/locales/es/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,29 +159,32 @@
"regla": "regla",
"grupo": "grupo",
"and": "and",
"or": "or"
"or": "or",
"source": "fuente"
},
"obs": {
"scene-list": "OBS | Lista de escenas",
"go-to-scene": "OBS | Ir a la escena",
"toggle-scene": "OBS | Cambiar entre dos escenas",
"toggle-audio": "OBS | Alternar fuente de audio",
"no-scene-selected": "Ninguna escena seleccionada",
"connect-at-startup": "Conectar a OBS al inicio",
"first-start-install-sentence": "Para conectar Marv a OBS, debes instalar OBS WebSocket.",
"overlay-install-sentence": "Para instalar Marv, crea una fuente del navegador en OBS con el enlance de abajo.",
"check-disable-source-not-visible": "Marque la casilla: Desactivar la fuente en la que no este visible.",
"check-refresh-browser-on-activate": "Marque la casilla: Recargar el navegador cuando la escena sea activa.",
"overlay-not-found": "Overlay no encontrado"
"overlay-not-found": "Overlay no encontrado",
"no-source-selected": "obs.no-source-selected"
},
"anime": {
"timeline": "Anime | Timeline"
"timeline": "Animación"
},
"twitch": {
"chat": "chat de Twitch",
"stream": "directo de Twitch",
"commands": "Comandos",
"rewards": "Recompensas",
"followers": "Partidarios",
"chat": "Twitch | Chat",
"stream": "Twitch | Stream",
"rewards": "Twitch | Recompensas",
"Followers": "Twitch | Seguidores",
"comandos": "Comandos",
"connect-at-startup": "Conectar a Twitch durante el inicio",
"empty-command-list": "Ningun comando",
"command-cooldown": "Cooldown \"{{command}}\" -> ~{{rest}}",
Expand Down
17 changes: 10 additions & 7 deletions app/static/locales/fr/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,29 +160,32 @@
"rule": "règle",
"group": "groupe",
"and": "et",
"or": "ou"
"or": "ou",
"source": "source"
},
"obs": {
"scene-list": "OBS | Liste des scènes",
"go-to-scene": "OBS | Aller à la scène",
"toggle-scene": "OBS | Basculer entre deux scènes",
"toggle-audio": "OBS | Basculer source audio",
"no-scene-selected": "Aucune scène sélectionnée",
"connect-at-startup": "Connecter OBS au démarrage",
"first-start-install-sentence": "Pour connecter Marv à OBS, tu dois installer OBS WebSocket.",
"overlay-install-sentence": "Pour installer Marv, créer une source navigateur dans OBS avec l'url ci-dessous.",
"check-disable-source-not-visible": "Coche la case: Désactiver la source quand elle n'est pas visible.",
"check-refresh-browser-on-activate": "Coche la case: Rafraîchir le navigateur lorsque la scène devient active.",
"overlay-not-found": "Overlay non trouvé"
"overlay-not-found": "Overlay non trouvé",
"no-source-selected": "obs.no-source-selected"
},
"anime": {
"timeline": "Anime | Timeline"
"timeline": "Animation"
},
"twitch": {
"chat": "Twitch chat",
"stream": "Twitch stream",
"chat": "Twitch | Chat",
"stream": "Twitch | Stream",
"rewards": "Twitch | Récompenses",
"followers": "Twitch | Supporters",
"commands": "Commandes",
"rewards": "Récompenses",
"followers": "Supporters",
"connect-at-startup": "Connecter Twitch au démarrage",
"empty-command-list": "Aucune commande",
"command-cooldown": "Cooldown \"{{command}}\" -> ~{{rest}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
export let data;
let none = _("words.none");
$: panel = data.panel;
$: widget = data.widget;
$: scenes = $state.scenes || [];
$: props = widget.component.props;
$: items = [_("words.none"), ...scenes.map((s) => s.name)];
$: items = [none, ...scenes.map((s) => s.name)];
function onChange({ detail: scene }) {
props.scene = scene;
function onChange({ detail }) {
props.scene = detail === none ? null : detail;
update(panel);
}
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script>
import { _ } from "@/libs/i18next";
import { state } from "@/stores/obs";
import { update } from "@/libs/panels";
import Select from "@/components/UI/Select.svelte";
export let data;
let sources = [];
let { panel, widget } = data;
let none = _("words.none");
$: if ($state.sources) {
sources = $state.sources.filter((source) => source.caps.hasAudio);
sources = [none, ...sources.map((source) => source.name)];
}
$: props = widget.component.props;
function onChange(key, { detail }) {
props[key] = detail === none ? null : detail;
update(panel);
}
</script>

<div class="p-2 pt-0 space-y-2 flex flex-auto flex-col">
<Select
items="{sources}"
value="{props.source}"
label="{_('words.source')}"
on:change="{onChange.bind(null, 'source')}"
/>
</div>
56 changes: 56 additions & 0 deletions front-src/client/components/Widgets/OBS/ToggleAudio/Widget.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script>
import obs from "@/api/obs";
import { onMount } from "svelte";
import { _ } from "@/libs/i18next";
import actions from "@/api/actions";
import Icon from "@/components/UI/Icon.svelte";
import MdVolumeUp from "svelte-icons/md/MdVolumeUp.svelte";
import MdVolumeOff from "svelte-icons/md/MdVolumeOff.svelte";
import WidgetWrapper from "@/components/Widgets/OBS/WidgetWrapper.svelte";
export let widget;
let state = null;
$: source = widget.component.props.source;
$: icon = state && !state.muted ? MdVolumeUp : MdVolumeOff;
$: if (source) {
obs.emit("GetVolume", { source }).then((result) => (state = result));
}
function onClick() {
actions.push({ type: "obs", widget }).catch((error) => {
console.log(">>>Error:", error);
});
}
onMount(async () => {
obs.on(`source.volume`, ({ sourceName, volume }) => {
if (sourceName === source) {
state.volume = volume;
}
});
obs.on(`source.muted`, ({ sourceName, muted }) => {
if (sourceName === source) {
state.muted = muted;
}
});
});
</script>

<WidgetWrapper widget="{widget}" on:click="{onClick}">
<div class="flex flex-col h-full text-center">
{#if source && state}
<div
class="flex flex-col w-full h-full {state.muted ? 'text-red-600' : ''}"
>
<div class="p-2 break-words">{source}</div>
<svelte:component this="{icon}" />
</div>
{:else}
<div class="p-2 bg-red-600">{_('obs.no-source-selected')}</div>
{/if}
</div>
</WidgetWrapper>
9 changes: 9 additions & 0 deletions front-src/client/components/Widgets/OBS/ToggleAudio/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
name: "ToggleAudio",
label: "obs.toggle-audio",
hasTrigger: true,
hasEvent: true,
props: {
source: null,
},
};
3 changes: 3 additions & 0 deletions front-src/client/components/Widgets/OBS/ToggleAudio/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as Settings } from "./Settings.svelte";
export { default as Widget } from "./Widget.svelte";
export { default as config } from "./config";
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
export let data;
let { panel, widget } = data;
let none = _("words.none");
$: scenes = $state.scenes || [];
$: props = widget.component.props;
$: items = [_("words.none"), ...scenes.map((s) => s.name)];
$: items = [none, ...scenes.map((s) => s.name)];
function onChange(key, { detail: scene }) {
props[key] = scene;
function onChange(key, { detail }) {
props[key] = detail === none ? null : detail;
update(panel);
}
</script>
Expand Down
12 changes: 8 additions & 4 deletions front-src/client/components/Widgets/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import * as TwitchCommands from "./Twitch/Commands";
import * as AnimeTimeline from "./Anime/Timeline";

import * as SceneList from "./OBS/SceneList";
import * as GoToScene from "./OBS/GoToScene";
import * as ToggleScene from "./OBS/ToggleScene";
import * as AnimeTimeline from "./Anime/Timeline";
import * as ToggleAudio from "./OBS/ToggleAudio";

import * as TwitchChat from "./Twitch/Chat";
import * as TwitchStream from "./Twitch/Stream";
import * as TwitchRewards from "./Twitch/Rewards";
import * as TwitchCommands from "./Twitch/Commands";
import * as TwitchFollowers from "./Twitch/Followers";

const widgets = {
AnimeTimeline,
TwitchCommands,
SceneList,
GoToScene,
ToggleScene,
AnimeTimeline,
ToggleAudio,
TwitchChat,
TwitchStream,
TwitchRewards,
TwitchCommands,
TwitchFollowers,
};

Expand Down

0 comments on commit 4c23b01

Please sign in to comment.