= Streamana

== Description

Streamana is a Web page which streams your camera and microphone to YouTube Live
(or any other HLS or DASH receiver). It uses https://github.com/davedoesdev/webm-muxer.js[webm-muxer.js] and
https://github.com/davedoesdev/ffmpeg.js[ffmpeg.js].

== Demo

You can see it in action http://rawgit.davedoesdev.com/davedoesdev/streamana/publish/site/streamana.html[here].
Use Chrome 95 or later.

. Get your ingestion URL from https://studio.youtube.com[YouTube Studio].
.. Click _CREATE_ and then select _Go Live_ from the drop-down menu.
.. Under _Select stream key_, select _Create new stream key_.
.. Give your key a name.
.. You must select _HLS_ as the streaming protocol. Note: YouTube DASH ingestion is only available
   by using the Youtube API. See https://developers.google.com/youtube/v3/live/guides/encoding-with-dash#url-structure[here]
   for more details.
.. Click _CREATE_.
.. Make sure the key you created is selected.
.. Click _COPY_ next to _Stream URL_.
. Paste the URL into the _Ingestion URL_ box in Streamana.
. Click _Live_.
** If you want to see what's happening under the hood, open developer tools (F12).
. To end the stream, click _Live_ again.

You can also change various options:

* Mute and unmute your microphone by clicking on the microphone symbol.
* Hide and show your camera by clicking on the camera symbol.
* Under the drop-down menu (top-left):
** Change the camera resolution.
** Convert your camera's video to greyscale.
** Lock the camera to portrait mode (where available, e.g. mobile phones).
** Zoom the camera to fill the page.
** Switch between HLS and DASH encoding.
** Switch between POST and PUT requests.
** Switch between CORS modes.
** Switch between https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder[MediaRecorder]
   and https://www.w3.org/TR/webcodecs/[WebCodecs].
** Select a different version of https://github.com/davedoesdev/ffmpeg.js[ffmpeg.js] to perform
   the HLS or DASH encoding.

== Customisation

You can change the look and feel of Streamana by editing link:site/streamana.html[]
and link:site/streamana.css[].

The camera video is passed through a WebGL fragment shader in link:site/shader.js[]
so you can change this to add video effects or overlays. The shader already handles
resizing and rotating the video in `main()`. The optional greyscale conversion is in
the `tpix()` function.

The page's functionality is defined in link:site/streamana.js[] and link:site/streamer.js[].

link:site/streamer.js[] exports a function, `get_default_config_from_url`, and a class,
`Streamer`, which does the heavy lifting.

You should first call `get_default_config_from_url`. It takes a single argument,
the URL of `ffmpeg-worker-hls.js` or `ffmpeg-worker-dash.js` in https://github.com/davedoesdev/ffmpeg.js[ffmpeg.js].
This allows your application (or the end user if required) to supply its own version,
in accordance with LGPL. It can be a relative path (i.e. just `ffmpeg-worker-hls.js` or
`ffmpeg-worker-dash.js`).

`get_default_config_from_url` determines the streaming protocol (`hls` or `dash`) and returns
the default configuration for the protocol:

```js
{
    ffmpeg_lib_url, // the URL you passed to `get_default_config_from_url`
    protocol, // `hls` or `dash`
    video: { // properies of the video you will be supplying
        bitrate: 2500 * 1000,
        framerate: 30
    },
    audio: { // properties of the audio you will be supplying
        bitrate: 128 * 1000
    },
    media_recorder: { // default options for MediaRecorder if it ends up being used
        video: {
            codec: protocol === 'dash' ? 'vp9' : 'H264', // video codec
        },
        audio: {
            codec: 'opus' // audio codec
        },
        webm: true, // container format
        mp4: false // if true, requires ffmpeg-worker-hls.js or ffmpeg-worker-dash.js
                   // to be configured with MP4 support (which is not the default)
    },
    webcodecs: { // default options for WebCodecs if it ends up being used
        video: {
            // video codec and options
            ...(protocol === 'dash' ? {
                codec: 'vp09.00.10.08.01'
            } : {
                codec: 'avc1.42E01E' /*'avc1.42001E'*/,
                avc: { format: 'annexb' }
            })
        },
        audio: {
            codec: 'opus' /*'pcm'*/, // audio codec
        },
        webm_muxer: { // options for webm-muxer.js
            video: {
                codec: protocol === 'dash' ? 'V_VP9' : 'V_MPEG4/ISO/AVC'
            },
            audio: {
                codec: 'A_OPUS',
                bit_depth: 0 // 32 for pcm */
            }
        }
    },
    ffmpeg: { // desired ffmpeg output codecs
        // Note: If the encoded stream already uses the desired codec then
        // it will pass `copy` instead. For example, if your browser encodes
        // your video to H.264 already then `copy` will be used instead of
        // `libx264`. This means you can use `ffmpeg-worker-hls.js` or
        // `ffmpeg-worker-dash.js` that doesn't contain a H.264 encoder.
        video: {
            codec: protocol === 'dash' ? 'libvpx-vp9' : 'libx264'
        },
        audio: {
            codec: protocol === 'dash' ? 'libopus' : 'aac'
        }
    }
};
```

You application can modify the returned configuration before creating a `Streamer` object.

Use the `Streamer` class as follows:

* The constructor takes the following arguments:
** The https://developer.mozilla.org/en-US/docs/Web/API/MediaStream[`MediaStream`]
   containing your video and audio tracks. Note that link:site/streamana.js[] supplies
   blank video when the camera is hidden and silent audio when the microphone is muted.
** An https://developer.mozilla.org/en-US/docs/Web/API/AudioContext[AudioContext] instance.
   This is used to create a persistent audio generator for triggering updates to
   avoid browser timer throttling. If you don't already use one in your application,
   you can just `new AudioContext()`.
** The ingestion URL.
** The configuration returned by calling `get_default_config_from_url` (see above),
   optionally modified by your application.
** Whether the video is rotated.
** Extra request options for https://developer.mozilla.org/en-US/docs/Web/API/fetch[`fetch`].
   You can use this to override the default request method (`POST`) or CORS mode (`no-cors`).
** Whether to encode audio and video using WebCodecs (`true`) or MediaRecorder (`false`).
* Call the `async start()` method to start streaming.
* Call the `end()` method to stop streaming.

`Streamer` extends from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget[`EventTarget`]
and dispatches the following events:

* `start` when streaming has started.
* `update`, dispatched frame rate times a second. link:site/streamana.js[] reacts to this
  event by refreshing the WebGL canvas from the camera.
* `exit` when streaming has stopped.
* `error` if an error occurs.

== Licence

Streamana is licensed under the terms of the link:LICENCE[MIT licence].

Note that https://github.com/davedoesdev/ffmpeg.js[ffmpeg.js] is licensed under LGPL.
Streamana runs it inside a Web Worker and communicates with it via message passing.
The end user can replace the version used by changing the URL in the user interface.

Note also that the https://github.com/davedoesdev/ffmpeg.js[ffmpeg.js] HLS and DASH
distributions contain no H.264 or MP4 code. All encoding is done by the browser using
https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder[`MediaRecorder`] or
https://www.w3.org/TR/webcodecs/[WebCodecs].
