0.0.30 • Published 4 months ago

partytracks v0.0.30

Weekly downloads
-
License
ISC
Repository
github
Last release
4 months ago

partytracks

Audio/video handling for realtime apps using Observables for WebRTC.

Features

  • Observable-based API - Better handling of WebRTC complexities
  • Automatic Recovery - Handles disconnects, hardware changes, and network switches
  • Abstracted Complexity - Application code doesn't need to handle WebRTC details

Why Observables?

A promise-based API (push a track, get a promise of metadata) seems simpler but proved to be a leaky abstraction when things go wrong. Sometimes a webcam is unplugged, or your peer connection drops when switching networks. Observables allow all of the logic of replacing/repairing tracks and connections to be contained within the library, allowing your application code to not be concerned with the details of WebRTC.

License

ISC

partytracks 🎶

A utility library for Cloudflare Calls built with RxJS Observables.

Example

Client code:

// needed to smooth out cross browser behavior inconsistencies
import "webrtc-adapter";

import { PartyTracks, resilientTrack$ } from "partytracks/client";
import { of } from "rxjs";

const localVideo = document.querySelector("video.local-video");
const remoteVideo = document.querySelector("video.remote-video");

// resilientTrack$ will follow a prioritized list of devices and
// try them in order, checking track health and re-evaluating
// when available devices change
const track$ = resilientTrack$({ kind: "videoinput" });

// Subscribe so that we can receive updates if the track changes,
// for example if a webcam is unplugged.
track$.subscribe((track) => {
  // Attach the webcam MediaStreamTrack to the "local video" for display
  const localMediaStream = new MediaStream();
  localMediaStream.addTrack(track);
  localVideo.srcObject = localMediaStream;
});

// Instantiate PartyTracks
const partyTracks = new PartyTracks();

// When pushing, you supply an Observable of a MediaStreamTrack, and you will
// receive an Observable of the metadata needed for someone else to pull that
// track. This metadata is a small POJO (Plain Old JavaScript Object) that can
// be serialized and sent to another user (usually via websocket).
const pushedTrackMetadata$ = partyTracks.push(track$);
// When pulling, you supply an Observable of the track metadata (from another
// user), and you will receive an Observable of that pulled MediaStreamTrack.
const pulledTrack$ = partyTracks.pull(pushedTrackMetadata$);

// Subscribing to the resulting Observable will trigger all of the WebRTC
// negotiation and the Observable will emit the track when it is ready.
const subscription = pulledTrack$.subscribe((track) => {
  // Attach the pulled MediaStreamTrack to the "remote video" for display
  const remoteMediaStream = new MediaStream();
  remoteMediaStream.addTrack(track);
  remoteVideo.srcObject = remoteMediaStream;
});

setTimeout(() => {
  // After 20 seconds, let's clean up by unsubscribing. This will close
  // the pulled track, and since our local demo is also pushing it will
  // close the pushed track as well since there are no other subscribers.
  subscription.unsubscribe();
}, 20000);

Server code:

In your server, you need to have a path that proxies all requests over to the Cloudflare Calls API and provides your app id and token. In a worker, it will look something like this:

import { Hono } from "hono";
import { routePartyTracksRequest } from "partytracks/server";

type Bindings = {
  CALLS_APP_ID: string;
  CALLS_APP_TOKEN: string;
};

const app = new Hono<{ Bindings: Bindings }>();

app.all("/partytracks/*", (c) =>
  routePartyTracksRequest({
    appId: c.env.CALLS_APP_ID,
    token: c.env.CALLS_APP_TOKEN,
    request: c.req.raw
  })
);

export default app;

React utils

If you're building using React, there are a few utilities you may find helpful.

By convention, Observables have a $ suffix to indicate that they're an Observable.

import {
  useObservableAsValue,
  useOnEmit,
  useValueAsObservable
} from "partytracks/react";

function SomeComponent({ value }) {
  // creates a stable observable that will
  // emit when a new value is passed in
  const value$ = useValueAsObservable(value);
  // subscribes and gives you the latest value
  // second arg is the default value if nothing
  // has been emitted yet
  const latestValue = useObservableAsValue(value$, "default value");
  // calls the callback whenever a value
  // is emitted
  useOnEmit(value$, (v) => console.log(v));
}
0.0.30

4 months ago

0.0.29

4 months ago

0.0.28

4 months ago

0.0.27

4 months ago

0.0.26

4 months ago

0.0.25

4 months ago

0.0.24

4 months ago

0.0.23

5 months ago

0.0.22

5 months ago

0.0.21

5 months ago

0.0.20

5 months ago

0.0.19

6 months ago

0.0.18

6 months ago

0.0.17

6 months ago

0.0.16

6 months ago

0.0.15

6 months ago

0.0.14

6 months ago

0.0.13

6 months ago

0.0.12

6 months ago

0.0.11

6 months ago

0.0.10

6 months ago

0.0.9

6 months ago

0.0.8

6 months ago

0.0.7

6 months ago

0.0.6

6 months ago

0.0.5

6 months ago

0.0.4

6 months ago

0.0.3

6 months ago

0.0.2

6 months ago

0.0.0

6 months ago