npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

partytracks

v0.0.36

Published

Integrate Cloudflare Calls into your PartyServer app

Downloads

504

Readme

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));
}