{"id":15063633,"url":"https://github.com/dwyl/dash-demo","last_synced_at":"2026-03-09T03:33:19.071Z","repository":{"id":248262754,"uuid":"828218159","full_name":"dwyl/DASH-demo","owner":"dwyl","description":"Minimal repo \u0026 Livebook to illustrate DASH live Streaming","archived":false,"fork":false,"pushed_at":"2024-07-13T22:17:25.000Z","size":35,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-01-15T13:48:55.266Z","etag":null,"topics":["elixir","livebook","mpeg-dash","streaming-server"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/dwyl.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-13T13:17:52.000Z","updated_at":"2024-07-13T22:17:28.000Z","dependencies_parsed_at":"2024-09-25T00:05:20.583Z","dependency_job_id":"81935155-c6d8-4643-a4ec-1166b9367ba3","html_url":"https://github.com/dwyl/DASH-demo","commit_stats":null,"previous_names":["dwyl/dash-demo"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2FDASH-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2FDASH-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2FDASH-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2FDASH-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dwyl","download_url":"https://codeload.github.com/dwyl/DASH-demo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241960810,"owners_count":20049340,"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":["elixir","livebook","mpeg-dash","streaming-server"],"created_at":"2024-09-25T00:05:07.968Z","updated_at":"2026-03-09T03:33:14.016Z","avatar_url":"https://github.com/dwyl.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MPEG-DASH with Elixir and Livebook\n\nThis repo demonstrates how to use the **D**ynamic **A**daptative **S**treaming over **H**TTP with a Livebook or by forking this repo.\n\nWe essentially use `FFmpeg` to build the DASH files (segments and a manifest file) and teh Javascript library [`dash.js`](https://github.com/Dash-Industry-Forum/dash.js).\n\nYou can fork the repo and run this code with `mix run --no-halt` or run the `Livebook` below.\n\n## Run this\n\nYou can fork and run this code with `mix run --no-halt`.\n\nYou can also run this Livebook.\nThe code is adapted and is a nice illustration on how to use `Kino.JS.Live`.\n\n[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdwyl%2FDASH-demo%2Fblob%2Fmain%2Flib%2Fdash-demo.livemd)\n\n\n## What is DASH?\n\n[DASH](https://www.mpeg.org/standards/MPEG-DASH/)\n\nDASH can be compared to `HLS`. This [repo](https://github.com/dwyl/HLS-demo) illustrates a usage of HLS with insertion of face recognition.\n\n### What is adaptative?\n\nThis means you produce video files with different quality levels, low, medium or high for example. This is done by invoquing the adequate FFmpeg command.\n\nWe did not illustrate this here.\n\n### Video containers and codecs and browsers\n\nVideo files use \"containers\" such a WEBM or MP4. Each use different codecs, and normally VP8/VP9 for the first, and H264/H265 (aka AV1) for the second for the video track.\n\nTypically, when you record your webcam feed, you use the [`MediaRecorder` API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder).\n\nYou can pass an option to specify the mimeType and codec. For example:\n\n```js\nconst options = { mimeType: \"video/mp4; codecs=avc1.42E01E\" };\n// or\nconst options = { mimeType: 'video/mp4; codecs=\"avc1.424028, mp4a.40.2\"' };\n// or\nconst options = { mimType: \"video/webm; codecs=vp8\" };\n//\nconst options = { mimeType: \"video/Webm; codec=vp9\" };\n```\n\nand use it:\n\n```js\nlet mediaRecorder = new MediaRecorder(stream, options);\n```\n\nThe evolution of codecs tend to produce smaller files or support 4K or 8k but at the expense of encoding time. Furthermore, some codecs are patented thus you may pay royalties to use them.\n\nThus the MediaRecorder in the browser might not be able to produce multimedia files (video \u0026 audio) in the given format or not be able to use the given codec.\n\nFor example:\n\n- Safari does not support WebM for MediaRecorder but supports MP4.\n- Firefox does not support MP4 with MediaRecorder.\n- Chrome supports MP4 for MediaRecorder with some difficulties.\n- Chrome and Firefox support WebM with hte codec VP8. Only Chrome supports the codec VP9, but not the successor AV1.\n\n### Video formats, a brief summary\n\nDifferent format of video formats you can encounter.\n\n1. MP4 (MPEG-4)\n   Container Format: stores video and audio.\n   Video codecs: H.264 (AVC), H.265 (HEVC)\n   Audio codecs: AAC, MP3\n\n2. WebM\n   Container Format: WebM is an open, royalty-free media file format designed for the web.\n   Video codecs: VP8, VP9, AV1\n   Audio codecs: Vorbis, Opus\n\n3. MKV (Matroska)\n   Container Format: MKV is an open standard free container format.\n   Video codecs: Any, but commonly H.264/AVC, H.265/HEVC, VP8, VP9\n   Audio codecs: AAC, MP3, Vorbis, FLAC\n\n4. AVI (Audio Video Interleave)\n   Container Format: AVI (Microsoft)\n   Video codecs: DivX, Xvid, H.264\n   Audio codecs: MP3, AC-3\n   Older format, less efficient compression compared to MP4 and WebM, widely supported.\n\n5. MOV\n   Container Format: MOV is a multimedia container format developed by Apple.\n   Video codecs: H.264 (AVC), H.265 (HEVC), ProRes\n   Audio codecs: AAC, ALAC\n   Features: Native to Apple devices, commonly used in professional video editing.\n\n6. IVF (Indeo Video File)\n   Container Format: IVF is a simple container format primarily used for encapsulating VP8 and VP9 video streams. Typically does not include Audio. Used for testing and development purposes.\n   Video codecs: VP8, VP9\n\n7. TS (Transport Stream), for HLS\n   It is packet based format (packets of length 168 bytes). It can multiplex (combine) multiple audio, video, and data streams into a single stream, allowing synchronized playback.\n   It has a built-in Error Correction mechanisms to improve reliability over unreliable transmission mediums.\n   Video codecs: H.264 (AVC), H.265 (HEVC)\n   Audio codecs: AAC, AC3\n\n## How can we solve this?\n\nYou may want to detect the user-agent in the browser. The [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent) strongly discourages this route and recommends to find an alternative.\n\nThe `MediaRecorder` has a static method [isTtypeSupported()](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported_static) to check whether it can support a given `mimeType`. We will use this.\n\nWe send it then through the WebSocket connection.\n\nIn code, this gives:\n\n```js\nlet mediaRecorder, mimetype;\nconst types = [\n  \"video/mp4; codecs=avc1.42E01E, mp4a.40.2\",\n  \"video/mp4; codecs=avc1.64001F, mp4a.40.2\",\n  \"video/mp4; codec=avc1.4D401F mp4a.40.2\",\n  \"video/webm;codecs=vp8, opus\",\n  \"video/webm;codecs=vp9, opus\",\n];\n\nfor (const type of types) {\n  if (MediaRecorder.isTypeSupported(type)) {\n    console.log(type);\n    mimetype = type.split(\";\")[0];\n    mediaRecorder = new MediaRecorder(stream, { mimeType: mimetype });\n  }\n}\n\nlet socket = new WebSocket(\n  `ws://localhost:4000/socket?csrf_token=${csrfToken}`\n);\n\nsocket.onopen = () =\u003e {\n  socket.send(JSON.stringify({ mimetype }));\n};\n```\n\n\u003e Safari accepts only MP4,\n\u003e Chrome accepts WEBM and MP4 (with difficulties),\n\u003e Firefox only WEBM.\n\nThe Websocket handler is:\n\n```elixir\ndefmodule WebSocketHandler do\n  @moduledoc false\n  @behaviour WebSock\n  require Logger\n\n  @impl true\n  def init(_args) do\n    dash_path = \"priv/dash\"\n    {:ok, pid_watcher} = GenServer.start(FileWatcher, [self(), dash_path])\n\n    state = %{\n      pid_watcher: pid_watcher,\n      pid_build: nil,\n      init: true\n    }\n\n    {:ok, state}\n  end\n\n  def handle_in(msg, [:opcode: :text], state) do\n    case Jason.decode(msg) do\n      {:ok, %{\"mimetype\" =\u003e mimetype}} -\u003e\n        Logger.warning(\"MIMETYPE: #{mimetype}\")\n        {:ok, pid_build} = FFmpegProcessor.start(state.dash_path, mimetype)\n        {:ok, %{state | pid_build: pid_build}}\n      {:error, msg} -\u003e\n        Logger.warning(\"ERROR: #{inspect(msg)}\")\n        {:stop, :shutdown, state}\n    end\n  end\n```\n\nwhere we pass the mime-type to the FFmpegProcessor:\n\n```elixir\ndefmodule FFmpegProcessor do\n  @moduledoc false\n\n  def start(path, mimetype) do\n\n    manifest = Path.join(path, \"manifest.mpd\")\n\n    build_cmd_webm =\n      ~w(ffmpeg -i pipe:0\n        -map 0\n        -codec: copy\n        -f dash\n        -use_timeline 1\n        -use_template 1\n        -init_seg_name init_$RepresentationID$.webm\n        -media_seg_name chunk_$RepresentationID$_$Number$.webm\n        #{manifest}\n      )\n\n      build_cmd_mp4 =\n        ~w(ffmpeg\n        -analyzeduration 100M -probesize 50M\n        -i pipe:0\n        -map 0\n        -c:v libx264\n        -preset fast\n        -crf 23\n        -c:a aac\n        -f dash\n        -use_timeline 1\n        -use_template 1\n        -init_seg_name init_$RepresentationID$.m4s\n        -media_seg_name chunk_$RepresentationID$_$Number$.m4s\n        #{manifest}\n      )\n\n    case mimetype do\n      \"video/mp4\" -\u003e\n        {:ok, _pid_build} =\n          ExCmd.Process.start_link(build_cmd_mp4, log: true)\n      \"video/webm\" -\u003e\n        {:ok, _pid_build} =\n          ExCmd.Process.start_link(build_cmd_webm, log: true)\n    end\n  end\nend\n```\n\nWhen we receive data from the browser (a video chunk), with:\n\n```js\nmediaRecorder.ondataavailable = async ({ data }) =\u003e {\n  if (!isReady) return;\n  if (data.size \u003e 0) {\n    console.log(data.size);\n    const buffer = await data.arrayBuffer();\n    if (socket.readyState === WebSocket.OPEN) {\n      socket.send(buffer);\n    }\n  }\n};\n```\n\nwe call the FFmpeg process:\n\n```elixir\ndef handle_in({msg, [opcode: :binary]}, state) do\n  Logger.debug(\"received data ---------------\")\n  %{pid_build: pid_build} = state\n  # Write the received binary data to the FFmpeg capture process\n  :ok = ExCmd.Process.write(pid_build, msg)\n  {:ok, state}\nend\n```\n\nOne difficulty is to get the right FFmpeg command, especially in the case of Chrome with mp4.\nThe command can be simplified when we change the order of the \"types\" array. When coded as above, the last \"true\" codec will be used, so \"VP9\" in case of Chrome, and \"vp8\" for Firefox\", and \"avc1\" for Safari.\n\nThe simplified FFmpeg command is:\n\n```elixir\nbuild_cmd_mp4 =\n  ~w(ffmpeg\n  -analyzeduration 100M -probesize 50M\n  -i pipe:0\n  -map 0\n  -codec: copy\n  -f dash\n  -use_timeline 1\n  -use_template 1\n  -init_seg_name init_$RepresentationID$.m4s\n  -media_seg_name chunk_$RepresentationID$_$Number$.m4s\n  #{manifest}\n)\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fdash-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdwyl%2Fdash-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fdash-demo/lists"}