Skip to main content

Vue Integration

Moq integrates seamlessly with Vue.js through web components and reactive composition. Build real-time streaming experiences in your Vue applications.

Installation

1
Install Packages
2
Install the necessary Moq packages:
3
npm install @moq/watch @moq/publish @moq/signals
4
Install Vue (if needed)
5
Ensure Vue is installed:
6
npm install vue

Using Web Components in Vue

Configure Vue for Custom Elements

Tell Vue to treat Moq elements as custom elements:
// main.ts
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

// Configure Vue to ignore custom elements
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('moq-');

app.mount('#app');
For Vite:
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('moq-')
        }
      }
    })
  ]
});

Watch Component

Use the <moq-watch> web component in Vue:
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import '@moq/watch/element';
import '@moq/watch/ui';

const relayUrl = ref('https://relay.example.com/anon');
const broadcastPath = ref('stream');
</script>

<template>
  <moq-watch-ui>
    <moq-watch
      :url="relayUrl"
      :path="broadcastPath"
      muted
      reload>
      <canvas style="width: 100%; height: auto" />
    </moq-watch>
  </moq-watch-ui>
</template>

Publish Component

Create a publishing component:
<script setup lang="ts">
import { ref } from 'vue';
import '@moq/publish/element';
import '@moq/publish/ui';

const relayUrl = ref('https://relay.example.com/anon');
const broadcastPath = ref('my-stream');
</script>

<template>
  <moq-publish-ui>
    <moq-publish
      :url="relayUrl"
      :path="broadcastPath"
      audio
      video>
      <video
        muted
        autoplay
        style="width: 100%; height: auto"
      />
    </moq-publish>
  </moq-publish-ui>
</template>

Reactive State with Composition API

Using Signals with Vue

Create reactive wrappers for Moq signals:
// composables/useMoqSignal.ts
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
import type { Signal } from '@moq/signals';

export function useMoqSignal<T>(signal: Signal<T>): Ref<T> {
  const value = ref(signal.get()) as Ref<T>;
  let cleanup: (() => void) | undefined;

  onMounted(() => {
    cleanup = signal.subscribe((newValue) => {
      value.value = newValue;
    });
  });

  onUnmounted(() => {
    cleanup?.();
  });

  return value;
}

Volume Control Component

Use the composable:
<script setup lang="ts">
import { computed } from 'vue';
import { useMoqSignal } from '@/composables/useMoqSignal';
import type { Watch } from '@moq/hang';

const props = defineProps<{
  watch: Watch;
}>();

const volume = useMoqSignal(props.watch.audio.volume);
const volumePercent = computed(() => Math.round(volume.value * 100));

const updateVolume = (event: Event) => {
  const target = event.target as HTMLInputElement;
  props.watch.audio.volume.set(parseFloat(target.value));
};
</script>

<template>
  <div class="volume-control">
    <label>Volume: {{ volumePercent }}%</label>
    <input
      type="range"
      min="0"
      max="1"
      step="0.01"
      :value="volume"
      @input="updateVolume"
    />
  </div>
</template>

<style scoped>
.volume-control {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
</style>

Controlling Web Components

Using Template Refs

Control the player programmatically:
<script setup lang="ts">
import { ref } from 'vue';
import '@moq/watch/element';

const watchElement = ref<HTMLElement | null>(null);

const togglePlay = () => {
  const element = watchElement.value;
  if (!element) return;

  const isPaused = element.hasAttribute('paused');
  if (isPaused) {
    element.removeAttribute('paused');
  } else {
    element.setAttribute('paused', '');
  }
};

const toggleMute = () => {
  const element = watchElement.value;
  if (!element) return;

  const isMuted = element.hasAttribute('muted');
  if (isMuted) {
    element.removeAttribute('muted');
  } else {
    element.setAttribute('muted', '');
  }
};
</script>

<template>
  <div>
    <moq-watch
      ref="watchElement"
      url="https://relay.example.com/anon"
      path="stream">
      <canvas style="width: 100%; height: auto" />
    </moq-watch>
    
    <div class="controls">
      <button @click="togglePlay">Play/Pause</button>
      <button @click="toggleMute">Mute/Unmute</button>
    </div>
  </div>
</template>

Complete Example

A full Vue application with Moq:
<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import '@moq/watch/element';
import '@moq/watch/ui';
import '@moq/publish/element';
import '@moq/publish/ui';

const relayUrl = ref('https://relay.example.com/anon');
const broadcastName = ref('demo');
</script>

<template>
  <div class="app">
    <header>
      <h1>Moq Vue Demo</h1>
      <input
        v-model="broadcastName"
        type="text"
        placeholder="Broadcast name"
      />
    </header>

    <div class="grid">
      <section>
        <h2>Publish</h2>
        <moq-publish-ui>
          <moq-publish
            :url="relayUrl"
            :path="broadcastName"
            audio
            video>
            <video muted autoplay style="width: 100%" />
          </moq-publish>
        </moq-publish-ui>
      </section>

      <section>
        <h2>Watch</h2>
        <moq-watch-ui>
          <moq-watch
            :url="relayUrl"
            :path="broadcastName"
            muted
            reload>
            <canvas style="width: 100%" />
          </moq-watch>
        </moq-watch-ui>
      </section>
    </div>
  </div>
</template>

<style scoped>
.app {
  max-width: 1400px;
  margin: 0 auto;
  padding: 20px;
}

header {
  margin-bottom: 30px;
}

header input {
  margin-left: 20px;
  padding: 8px 12px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 30px;
}

@media (max-width: 768px) {
  .grid {
    grid-template-columns: 1fr;
  }
}

section h2 {
  margin-bottom: 15px;
}
</style>

Using with Nuxt

For Nuxt applications, configure custom elements:
// nuxt.config.ts
export default defineNuxtConfig({
  vue: {
    compilerOptions: {
      isCustomElement: (tag) => tag.startsWith('moq-')
    }
  }
});
Create a client-only component:
<!-- components/MoqPlayer.client.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';

const loaded = ref(false);

onMounted(async () => {
  await import('@moq/watch/element');
  await import('@moq/watch/ui');
  loaded.value = true;
});

const props = defineProps<{
  url: string;
  path: string;
}>();
</script>

<template>
  <moq-watch-ui v-if="loaded">
    <moq-watch :url="props.url" :path="props.path" muted reload>
      <canvas style="width: 100%" />
    </moq-watch>
  </moq-watch-ui>
</template>
Use with ClientOnly:
<template>
  <ClientOnly>
    <MoqPlayer url="https://relay.example.com/anon" path="stream" />
  </ClientOnly>
</template>

Best Practices

State Management with Pinia

Manage Moq state globally:
// stores/moq.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useMoqStore = defineStore('moq', () => {
  const relayUrl = ref('https://relay.example.com/anon');
  const isConnected = ref(false);

  function setRelayUrl(url: string) {
    relayUrl.value = url;
  }

  function setConnected(connected: boolean) {
    isConnected.value = connected;
  }

  return {
    relayUrl,
    isConnected,
    setRelayUrl,
    setConnected
  };
});

Error Handling

Handle connection errors gracefully:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

const error = ref<string | null>(null);

const handleError = (e: Event) => {
  error.value = 'Failed to connect to stream';
};

onMounted(() => {
  window.addEventListener('moq-error', handleError);
});

onUnmounted(() => {
  window.removeEventListener('moq-error', handleError);
});
</script>

<template>
  <div>
    <div v-if="error" class="error">
      {{ error }}
    </div>
    <moq-watch url="..." path="...">
      <canvas style="width: 100%" />
    </moq-watch>
  </div>
</template>

<style scoped>
.error {
  padding: 12px;
  margin-bottom: 16px;
  background: #fee;
  border: 1px solid #fcc;
  border-radius: 4px;
  color: #c00;
}
</style>

TypeScript Support

Add type declarations:
// types/moq.d.ts
declare module '@moq/watch/element' {
  const content: any;
  export default content;
}

declare module '@moq/watch/ui' {
  const content: any;
  export default content;
}

declare module '@moq/publish/element' {
  const content: any;
  export default content;
}

declare module '@moq/publish/ui' {
  const content: any;
  export default content;
}

Next Steps

React Integration

Use Moq with React

JavaScript API

Advanced programmatic control

Authentication

Secure your streams

Web Components

Framework-agnostic usage

Build docs developers (and LLMs) love