{"id":15412909,"url":"https://github.com/ascorbic/chalkstream","last_synced_at":"2025-07-10T17:05:45.814Z","repository":{"id":207802013,"uuid":"670302855","full_name":"ascorbic/chalkstream","owner":"ascorbic","description":"chalkstream","archived":false,"fork":false,"pushed_at":"2023-11-17T16:53:10.000Z","size":360,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-13T11:51:07.760Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ascorbic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-07-24T18:38:26.000Z","updated_at":"2024-09-05T06:26:19.000Z","dependencies_parsed_at":"2023-11-17T17:55:59.878Z","dependency_job_id":null,"html_url":"https://github.com/ascorbic/chalkstream","commit_stats":null,"previous_names":["ascorbic/chalkstream"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ascorbic/chalkstream","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Fchalkstream","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Fchalkstream/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Fchalkstream/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Fchalkstream/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ascorbic","download_url":"https://codeload.github.com/ascorbic/chalkstream/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Fchalkstream/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264614492,"owners_count":23637603,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T16:54:49.189Z","updated_at":"2025-07-10T17:05:45.796Z","avatar_url":"https://github.com/ascorbic.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n  \n\u003cimg src=\"https://github.com/ascorbic/chalkstream/blob/main/sites/vanilla/public/stream.png?raw=true\" width=\"128\" height=\"128\" alt=\"Chalkstream\" /\u003e\n\n# Chalkstream\n\n## Serverless live streaming\n\n\u003ca href=\"https://app.netlify.com/start/deploy?repository=https://github.com/ascorbic/chalkstream-template\"\u003e\u003cimg src=\"https://www.netlify.com/img/deploy/button.svg\" alt=\"Deploy to Netlify\"\u003e\u003c/a\u003e\n\n\u003c/div\u003e\n\nChalkstream is an open source, self-hosted live streaming \"server\" that runs on\nserverless edge functions. You can stream right from the browser, with a public\nlink to share with your friends. It uses Netlify Edge Functions to ingest and\nserve HLS streams, and the chunks are stored in Netlify Blobs. Control your own\ndata.\n\n## How it works\n\nHLS is a streaming protocol created by Apple that doesn't require a special\nserver to stream. It works by splitting the video into small chunks, and serving\nthem in a playlist. Chalkstream does this encoding in the browser using a\nWebAssembly build of FFmpeg. This is a lot quicker if your browser natively\nsupports H.264 (most except Firefox). Otherwise your computer needs to be quite\nfast if it is to handle real-time encoding. The chunks are then uploaded to\nNetlify Blobs. Netlify Edge Functions handle the ingest and generating the\ndynamic playlist.\n\n**[See a demo](https://chalkstream-astro.netlify.app/)**\n\nWhen you load the broadcast page it generates a random stream ID, and once you\nstart streaming you can share a page with the live stream. HLS streams can be\nplayed in any modern browser, either natively or via Media Source Extensions and\nHls.js. Players such as Video.js and react-player make this easy - see\n[the demo sites](https://github.com/ascorbic/chalkstream/tree/main/sites) for\nexamples.\n\n## Usage\n\nChalkstream supports vanilla HTML+JS, plus React. For the vanilla version you\ncan either install it from npm and use it with a bundler or load it from a CDN\ndirectly in the HTML page. For React, you can install it and import the\ncomponent.\n\n### From a CDN\n\nYou can load the library directly from a CDN. This is the simplest way to get\nstarted.\n\n```html\n\u003cbody\u003e\n  \u003cvideo id=\"myself\" autoplay muted\u003e\u003c/video\u003e\n  \u003cdiv id=\"controls\" hidden\u003e\n    \u003cbutton id=\"start\"\u003eStart streaming\u003c/button\u003e\n    \u003ca id=\"playback-link\" target=\"_blank\"\u003ePlayback link\u003c/a\u003e\n  \u003c/div\u003e\n  \u003cscript type=\"module\"\u003e\n    import { ChalkStream } from \"https://cdn.jsdelivr.net/npm/chalkstream\";\n\n    const stream = new ChalkStream({\n      onReady: async (streamId) =\u003e {\n        document.getElementById(\"controls\").hidden = false;\n        const playback = document.getElementById(\"playback-link\");\n        playback.href = `/play/${streamId}`;\n      },\n      videoElement: document.getElementById(\"myself\"),\n      onError: (error) =\u003e {\n        console.error(error);\n      },\n    });\n\n    const start = document.getElementById(\"start\");\n\n    start.addEventListener(\"click\", async () =\u003e {\n      if (!stream.isStreaming) {\n        stream.start();\n        start.textContent = \"Stop\";\n      } else {\n        stream.stop();\n        start.textContent = \"Start\";\n      }\n    });\n\n    stream.init();\n  \u003c/script\u003e\n\u003c/body\u003e\n```\n\n### With a bundler\n\nInstall the package:\n\n```sh\nnpm install chalkstream\n```\n\nThen import it:\n\n```typescript\nimport { ChalkStream } from \"chalkstream\";\n\nconst stream = new ChalkStream({\n  onReady: async (streamId) =\u003e {\n    document.getElementById(\"controls\").hidden = false;\n    const playback = document.getElementById(\"playback-link\");\n    playback.href = `/play/${streamId}`;\n  },\n  videoElement: document.getElementById(\"myself\"),\n  onError: (error) =\u003e {\n    console.error(error);\n  },\n});\n```\n\n### With React\n\nInstall the package:\n\n```sh\nnpm install chalkstream\n```\n\nThen import the component:\n\n```typescript\nimport { ChalkStreamVideo, type ChalkStreamRef } from \"chalkstream/react\";\n\nexport const VideoPlayer = () =\u003e {\n  const [streamId, setStreamId] = useState\u003cstring | null\u003e(null);\n  const chalkstreamRef = useRef\u003cChalkStreamRef\u003e(null);\n\n  const togglePlayback = () =\u003e {\n    if (chalkstreamRef.current?.isStreaming) {\n      chalkstreamRef.current?.stop();\n    } else {\n      chalkstreamRef.current?.start();\n    }\n  };\n\n  return (\n    \u003cdiv\u003e\n      \u003cChalkStreamVideo onReady={setStreamId} ref={ChalkStreamRef} /\u003e\n      {streamId ? (\n        \u003cdiv id=\"controls\"\u003e\n          \u003cbutton onClick={togglePlayback}\u003e⏯️\u003c/button\u003e\n          \u003ca href={`/play/${streamId}`} target=\"_blank\"\u003e\n            Playback link\n          \u003c/a\u003e\n        \u003c/div\u003e\n      ) : null}\n    \u003c/div\u003e\n  );\n};\n```\n\n## Edge functions\n\nHowever you build the site, you need to include three edge functions in the\n`netlify/edge-functions` directory. These are `ingest`, `chunk` and `manifest`.\nThese just re-export the edge functions from the `chalkstream/edge` module.\nEnsure that you have the correct config for each function so that the paths\nmatch.\n\n```typescript\n// netlify/edge-functions/ingest.ts\nimport type { Context, Config } from \"@netlify/edge-functions\";\n\nexport { ingestHandler as default } from \"https://esm.sh/chalkstream/edge\";\n\nexport const config: Config = {\n  method: \"PUT\",\n  path: \"/ingest/:session/:digest.ts\",\n};\n```\n\n```typescript\n// netlify/edge-functions/chunk.ts\nimport type { Context, Config } from \"@netlify/edge-functions\";\n\nexport { chunkHandler as default } from \"https://esm.sh/chalkstream/edge\";\n\nexport const config: Config = {\n  method: \"GET\",\n  path: \"/chunk/:session/:digest.ts\",\n};\n```\n\n```typescript\n// netlify/edge-functions/manifest.ts\nimport type { Context, Config } from \"@netlify/edge-functions\";\n\nexport { manifestHandler as default } from \"https://esm.sh/chalkstream/edge\";\n\nexport const config: Config = {\n  method: \"GET\",\n  path: \"/playlist/:session.m3u8\",\n};\n```\n\n## API\n\n### `ChalkStream`\n\nThis is the main class. You either instantiate it, passing it a config object\nand HTMLVideoElement, or you can use the React component which returns the\nobject in a ref.\n\n#### `new ChalkStream(config: ChalkStreamConfig)`\n\n## Security\n\nBy default, anybody can create a stream, and anybody can view it.\n\nIf you deploy the site yourself you should restrict access to prevent abuse. To\nhelp with this, you can pass an \"authorization\" prop to the Chalkstream\nconsturctor, and it will be sent to the ingest endpoint as a bearer token. You\nwill need to check that header yourself at the moment!\n\nAny completed stream can be viewed on-demand automatically. A stream is\nconsidered complete if no chunks have been received for 30 seconds, but you can\nresume streaming with the same session id and it will continue where it left\noff.\n\nAll streams are public by default, and anyone can access the ingest endpoint. If\nyou want to do anything serious you should restrict access. The playback\nendpoints require knowing the stream id, which is a hash of the session ID.\nAnyone who knows the session ID can record to that stream, so don't share it!\nDon't use your stream to record state secrets or recite your wallet seed phrase.\n\n### 1. Deploy the site\n\n\u003ca href=\"https://app.netlify.com/start/deploy?repository=https://github.com/ascorbic/chalkstream-template\"\u003e\n  \u003cimg src=\"https://www.netlify.com/img/deploy/button.svg\" alt=\"Deploy to Netlify\" /\u003e\n\u003c/a\u003e\n\nClone the repo, or use the button above to deploy the site to Netlify.\n\n### 2. Customise the site, player etc\n\n### Copyright\n\nCopyright Matt Kane 2023. Chalkstream is released under the MIT license.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fascorbic%2Fchalkstream","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fascorbic%2Fchalkstream","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fascorbic%2Fchalkstream/lists"}