# Delete an asset Source: https://docs.livepeer.org/v1/api-reference/asset/delete DELETE /asset/{assetId} # Retrieve an asset Source: https://docs.livepeer.org/v1/api-reference/asset/get GET /asset/{assetId} # Retrieve assets Source: https://docs.livepeer.org/v1/api-reference/asset/get-all GET /asset # Overview Source: https://docs.livepeer.org/v1/api-reference/asset/overview The Assets API is used to create, retrieve, update, delete assets object from pipeline. ### Asset object This is a unique identifier for the asset. Type of the asset. This can be either 'video' or 'audio'. Used to form playback URL and storage folder. Whether to generate MP4s for the asset. Attach Livepeer Studio C2PA Attestation in the output mp4 video. URL for HLS playback. URL to manually download the asset if desired. Reference to the playback-policy schema. Source of the asset, which can be URL, recording, directUpload, or clip. Reference to the creator-id schema. Information about the storage of the asset, particularly on IPFS. Status of the asset, including its phase, update time, and any error message. Name of the asset. This is not necessarily the filename, can be a custom name or title. Timestamp (in milliseconds) at which asset was created. Size of the asset in bytes. Hash of the asset. Video metadata including format, duration, bitrate, and tracks information. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "type": "video", "playbackId": "eaw4nk06ts2d0mzb", "staticMp4": "boolean", "c2pa": "boolean", "playbackUrl": "https://livepeercdn.com/asset/ea03f37e-f861-4cdd-b495-0e60b6d753ad/index.m3u8", "downloadUrl": "https://livepeercdn.com/asset/eaw4nk06ts2d0mzb/video", "playbackPolicy": { "type": "public", "webhookId": "3e02c844-d364-4d48-b401-24b2773b5d6c", "webhookContext": { "foo": "bar" } }, "source": { "type": "url", "url": "https://example.com/video.mp4", "gatewayUrl": "https://example.com/video.mp4" }, "creatorId": "object", "storage": { "ipfs": { "spec": { "nftMetadataTemplate": "file", "nftMetadata": { "name": "My NFT", "description": "My NFT description" } } } }, "status": { "phase": "ready", "updatedAt": 1587667174725, "progress": 100 }, "name": "filename.mp4", "createdAt": 1587667174725, "size": 84934509, "hash": [ { "hash": "9b560b28b85378a5004117539196ab24e21bbd75b0e9eb1a8bc7c5fd80dc5b57", "algorithm": "sha256" } ], "videoSpec": { "format": "mp4", "duration": 23.8328, "bitrate": 1000000, "tracks": [ { "type": "video", "codec": "h264", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "width": 1920, "height": 1080, "pixelFormat": "yuv420p", "fps": 30 }, { "type": "audio", "codec": "aac", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "channels": 2, "sampleRate": 44100, "bitDepth": 16 } ] } } ``` # Update an asset Source: https://docs.livepeer.org/v1/api-reference/asset/update PATCH /asset/{assetId} # Upload an asset Source: https://docs.livepeer.org/v1/api-reference/asset/upload POST /asset/request-upload To upload an asset, your first need to request for a direct upload URL and only then actually upload the contents of the asset. \ \ Once you created a upload link, you have 2 options, resumable or direct upload. For a more reliable experience, you should use resumable uploads which will work better for users with unreliable or slow network connections. If you want a simpler implementation though, you should just use a direct upload. ## Direct Upload For a direct upload, make a PUT request to the URL received in the url field of the response above, with the raw video file as the request body. response above: ## Resumable Upload Livepeer supports resumable uploads via Tus. This section provides a simple example of how to use tus-js-client to upload a video file. \ \ From the previous section, we generated a URL to upload a video file to Livepeer on POST /api/asset/request-upload. You should use the tusEndpoint field of the response to upload the video file and track the progress: ``` # This assumes there is an `input` element of `type="file"` with id `fileInput` in the HTML const input = document.getElementById('fileInput'); const file = input.files[0]; const upload = new tus.Upload(file, { endpoint: tusEndpoint, // URL from `tusEndpoint` field in the `/request-upload` response metadata: { filename, filetype: 'video/mp4', }, uploadSize: file.size, onError(err) { console.error('Error uploading file:', err); }, onProgress(bytesUploaded, bytesTotal) { const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); console.log('Uploaded ' + percentage + '%'); }, onSuccess() { console.log('Upload finished:', upload.url); }, }); const previousUploads = await upload.findPreviousUploads(); if (previousUploads.length > 0) { upload.resumeFromPreviousUpload(previousUploads[0]); } upload.start(); ``` > Note: If you are using tus from node.js, you need to add a custom URL storage to enable resuming from previous uploads. On the browser, this is enabled by default using local storage. In node.js, add urlStorage: new tus.FileUrlStorage("path/to/tmp/file"), to the UploadFile object definition above. # Upload asset via URL Source: https://docs.livepeer.org/v1/api-reference/asset/upload-via-url POST /asset/upload/url # Audio To Text Source: https://docs.livepeer.org/v1/api-reference/generate/audio-to-text POST /api/beta/generate/audio-to-text # Image To Image Source: https://docs.livepeer.org/v1/api-reference/generate/image-to-image POST /api/beta/generate/image-to-image # Image To Video Source: https://docs.livepeer.org/v1/api-reference/generate/image-to-video POST /api/beta/generate/image-to-video # LLM Source: https://docs.livepeer.org/v1/api-reference/generate/llm ## Overview The `llm` pipeline provides an OpenAI-compatible interface for text generation, designed to integrate seamlessly into media workflows. ## Models The `llm` pipeline supports **any Hugging Face-compatible LLM model**. Since models evolve quickly, the set of warm (preloaded) models on Orchestrators changes regularly. To see which models are currently available, check the [Network Capabilities dashboard](https://tools.livepeer.cloud/ai/network-capabilities).\ At the time of writing, the most commonly available model is [meta-llama/Meta-Llama-3.1-8B-Instruct](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct). For faster responses with different [LLM](https://huggingface.co/models?pipeline_tag=text-generation) diffusion models, ask Orchestrators to load it on their GPU via the `ai-video` channel in [Discord Server](https://discord.gg/livepeer). ## Basic Usage Instructions For a detailed understanding of the `llm` endpoint and to experiment with the API, see the [Livepeer AI API Reference](/ai/api-reference/llm). To generate text with the `llm` pipeline, send a `POST` request to the Gateway's `llm` API endpoint: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X POST "https:///llm" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Meta-Llama-3.1-8B-Instruct", "messages": [ { "role": "user", "content": "Tell a robot story." } ] }' ``` In this command: * `` should be replaced with your AI Gateway's IP address. * `` should be replaced with your API token. * `model` is the LLM model to use for generation. * `messages` is the conversation or prompt input for the model. For additional optional parameters such as `temperature`, `max_tokens`, or `stream`, refer to the [Livepeer AI API Reference](/ai/api-reference/llm). After execution, the Orchestrator processes the request and returns the response to the Gateway: ```json theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "chatcmpl-abc123", "model": "meta-llama/Meta-Llama-3.1-8B-Instruct", "choices": [ { "message": { "role": "assistant", "content": "Once upon a time, in a gleaming city of circuits..." } } ] } ``` By default, responses are returned as a single JSON object. To stream output token-by-token using **Server-Sent Events (SSE)**, set `"stream": true` in the request body. ## Orchestrator Configuration To configure your Orchestrator to serve the `llm` pipeline, refer to the [Orchestrator Configuration](/ai/orchestrators/get-started) guide. ### Tuning Environment Variables The `llm` pipeline supports several environment variables that can be adjusted to optimize performance based on your hardware and workload. These are particularly helpful for managing memory usage and parallelism when running large models. Enables 8-bit quantization using `bitsandbytes` for lower memory usage. Set to `true` to enable. Defaults to `false`. Number of pipeline parallel stages. Should not exceed the number of model layers. Defaults to `1`. Number of tensor parallel units. Must divide evenly into the number of attention heads in the model. Defaults to `1`. Maximum number of tokens per input sequence. Defaults to `8192`. Maximum number of tokens processed in a single batch. Should be greater than or equal to `MAX_MODEL_LEN`. Defaults to `8192`. Maximum number of sequences processed per batch. Defaults to `128`. Target GPU memory utilization as a float between `0` and `1`. Higher values make fuller use of GPU memory. Defaults to `0.97`. ### System Requirements The following system requirements are recommended for optimal performance: * [NVIDIA GPU](https://developer.nvidia.com/cuda-gpus) with **at least 16GB** of VRAM. ## Recommended Pipeline Pricing We are planning to simplify the pricing in the future so orchestrators can set one AI price per compute unit and have the system automatically scale based on the model's compute requirements. The `/llm` pipeline is currently priced based on the **maximum output tokens** specified in the request — not actual usage — due to current payment system limitations. We're actively working to support usage-based pricing to better align with industry standards. The LLM pricing landscape is highly competitive and rapidly evolving. Orchestrators should set prices based on their infrastructure costs and [market positioning](https://llmpricecheck.com/). As a reference, inference on `llama-3-8b-instruct` is currently around `0.08 USD` per 1 million **output tokens**. ## API Reference Explore the `llm` endpoint and experiment with the API in the Livepeer AI API Reference. # Overview Source: https://docs.livepeer.org/v1/api-reference/generate/overview The Generate API is used to run generative AI models. These APIs implement the [Livepeer AI Gateway API spec](/ai/api-reference) and are served under the Studio platform suite. They are prefixed with `/api/beta/generate` and are used to run AI models to generate content. # Segment Anything 2 Source: https://docs.livepeer.org/v1/api-reference/generate/segment-anything-2 POST /api/beta/generate/segment-anything-2 # Text To Image Source: https://docs.livepeer.org/v1/api-reference/generate/text-to-image POST /api/beta/generate/text-to-image # Upscale Source: https://docs.livepeer.org/v1/api-reference/generate/upscale POST /api/beta/generate/upscale # Create a multistream Source: https://docs.livepeer.org/v1/api-reference/multistream/create POST /multistream/target # Delete a multistream Source: https://docs.livepeer.org/v1/api-reference/multistream/delete DELETE /multistream/target/{id} Make sure to remove any references to the target on existing streams before actually deleting it from the API. # Retrieve a multistream Source: https://docs.livepeer.org/v1/api-reference/multistream/get GET /multistream/target/{id} # Retrieve all multistreams Source: https://docs.livepeer.org/v1/api-reference/multistream/get-all GET /multistream/target # Overview Source: https://docs.livepeer.org/v1/api-reference/multistream/overview The Multistream target API is used to create, retrieve, update, delete multi-stream targets object from pipeline. ### Multistream target object Unique identifier for the multistream target. Name of the multistream target. Livepeer-compatible multistream target URL (RTMP(S) or SRT). This URL is used for streaming to the target platform. Indicates if this multistream target is disabled. If true, it will not be used for pushing even if configured in a stream object. Timestamp (in milliseconds) at which the multistream target object was created. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "name": "My Multistream Target", "url": "rtmps://live.my-service.tv/channel/secretKey", "disabled": "boolean", "createdAt": 1587667174725 } ``` # Update a multistream Source: https://docs.livepeer.org/v1/api-reference/multistream/update PATCH /multistream/target/{id} # Authentication Source: https://docs.livepeer.org/v1/api-reference/overview/authentication Learn more about Livepeer's API. Livepeer API uses API keys to verify and authorize requests. You can manage and review your API keys through Livepeer Studio. You need to pass your API key in the `Authorization` header with a `Bearer` prefix while sending a request. ``` Bearer YOUR_API_KEY ``` It's important to note that your API keys come with significant privileges, so it's essential to keep them safe and secure! Refrain from sharing your secret API keys in GitHub or other publicly accessible places. By default, API keys can only be used from a backend server. This is to ensure maximum security and prevent that you accidentally expose your account by including the secret API key in a public web page. ### CORS-Enabled Keys Please read the below documentation in its entirety before using CORS-enabled API keys. **There is a different security model for CORS keys.** Studio supports the creation of CORS-enabled API keys. This is a special option when generating an API key which allows a webpage to make requests **directly** to Studio, as opposed to coming from your backend. #### Security with CORS Keys **The security model is different for CORS-enabled API keys.** Since any user has access to these keys, the IDs of assets and streams **must** be kept secret from anyone who should not have admin control over them. For instance, a viewer should only have access to the playback ID, since knowing the asset ID (together with the CORS-enabled API key, which is embedded in the webpage) allows them to make changes to the asset. This is the same for streams - if a user has access to a stream ID alongside the CORS API key, they can modify the stream or view the stream key. If a viewer had access to the stream ID + CORS API key, they could hijack the stream. **A `playbackId` should be exposed to the viewer only.** cors api key # Introduction Source: https://docs.livepeer.org/v1/api-reference/overview/introduction Learn more about Livepeer's API. Welcome to the Livepeer API reference docs! Here you'll find all the endpoints exposed on the standard Livepeer API, learn how to use them and what they return. The Livepeer API is organized around REST, has predictable resource-oriented URLs, accepts JSON request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs. # Overview Source: https://docs.livepeer.org/v1/api-reference/playback/overview The Playback API is used to retrieve playback object from pipeline. ### Playback Object Type of playback, such as live, vod, or recording. Metadata for the playback information. This includes details about the source, playback policy, and attestation. Indicates if the playback is live (1) or not (0). Reference to the playback-policy schema. Array of source objects detailing the playback sources. Each source includes HRN, type, URL, size, width, height, and bitrate. Human-readable name (HRN) for the playback source format. MIME type of the playback source. URL for the playback source. Size of the playback source file. Width of the video in pixels. Height of the video in pixels. Bitrate of the playback source. Reference to the attestation schema. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "type": "vod", "meta": { "live": 0, "playbackPolicy": { "type": "public", "webhookId": "3e02c844-d364-4d48-b401-24b2773b5d6c", "webhookContext": { "foo": "bar" } }, "source": [ { "hrn": "MP4", "type": "html5/video/mp4", "url": "https://asset-cdn.lp-playback.studio/hls/1bde4o2i6xycudoy/static360p0.mp4", "size": 494778, "width": 204, "height": 360, "bitrate": 449890 } ], "attestation": { "id": "5b9e63bb-6fd0-4bea-aff2-cc5d4eb9cad0", "primaryType": "VideoAttestation", "domain": { "name": "Verifiable Video", "version": "1" }, "message": { "video": "string", "attestations": [ { "role": "string", "address": "string" } ], "signer": "string", "timestamp": "number" }, "signature": "string", "createdAt": 1587667174725, "signatureType": "eip712", "storage": { "ipfs": { "cid": "bafybeihoqtemwitqajy6d654tmghqqvxmzgblddj2egst6yilplr5num6u", "url": "ipfs://bafybeihoqtemwitqajy6d654tmghqqvxmzgblddj2egst6yilplr5num6u", "gatewayUrl": "https://ipfs.io", "updatedAt": 1587667174725 }, "status": { "phase": "ready", "progress": 0.5, "errorMessage": "Failed to export to IPFS", "tasks": { "pending": "string", "last": "string", "failed": "string" } } } } } } ``` # Retrieve a session Source: https://docs.livepeer.org/v1/api-reference/session/get GET /session/{id} # Retrieve all sessions Source: https://docs.livepeer.org/v1/api-reference/session/get-all GET /session # Retrieve clips of a session Source: https://docs.livepeer.org/v1/api-reference/session/get-clip GET /session/{id}/clips # Retrieve recorded sessions Source: https://docs.livepeer.org/v1/api-reference/session/get-recording GET /stream/{parentId}/sessions # Overview Source: https://docs.livepeer.org/v1/api-reference/session/overview The Sessions API is used to retrieve sessions object from pipeline. ### Session Object Unique identifier for the session. Name of the session. Timestamp of the last activity in the session. Number of source segments in the session. Number of transcoded segments in the session. Duration of all the source segments in seconds. Duration of all the transcoded segments in seconds. Total bytes of source segments. Total bytes of transcoded segments. Rate at which sourceBytes increases (bytes/second). Rate at which transcodedBytes increases (bytes/second). Reference to the stream-health-payload schema for health status. Reference to the stream-health-payload schema for human-readable issues. Timestamp (in milliseconds) at which the session was created. Identifier of the parent stream object. Indicates whether the stream should be recorded. Status of the recording process of this stream session. URL for accessing the recording of this stream session. URL for the stream session recording packaged in an MP4 format. Identifier used to form the playback URL. Array of profiles detailing various streaming qualities. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "de7818e7-610a-4057-8f6f-b785dc1e6f88", "name": "test_session", "lastSeen": 1587667174725, "sourceSegments": 1, "transcodedSegments": 2, "sourceSegmentsDuration": 1, "transcodedSegmentsDuration": 2, "sourceBytes": 1, "transcodedBytes": 2, "ingestRate": 1, "outgoingRate": 2, "isHealthy": true, "issues": [ "Buffer underflow", "Network congestion" ], "createdAt": 1587667174725, "parentId": "de7818e7-610a-4057-8f6f-b785dc1e6f88", "record": "boolean", "recordingStatus": "waiting", "recordingUrl": "https://lp-playback.com/hls/29eb9byolvwdbkue/index.m3u8", "mp4Url": "https://lp-playback.com/hls/29eb9byolvwdqkue/720.mp4", "playbackId": "eaw4nk06ts2d0mzb", "profiles": [ { "name": "720p", "width": 1280, "height": 720, "bitrate": 3000, "fps": 30 } ] } ``` # Create a signing key Source: https://docs.livepeer.org/v1/api-reference/signing-key/create POST /access-control/signing-key The publicKey is a representation of the public key, encoded as base 64 and is passed as a string, and the privateKey is displayed only on creation. This is the only moment where the client can save the private key, otherwise it will be lost. Remember to decode your string when signing JWTs. Up to 10 signing keys can be generated, after that you must delete at least one signing key to create a new one. # Delete a signing key Source: https://docs.livepeer.org/v1/api-reference/signing-key/delete DELETE /access-control/signing-key/{keyId} # Retrieve a signing key Source: https://docs.livepeer.org/v1/api-reference/signing-key/get GET /access-control/signing-key/{keyId} # Retrieve signing keys Source: https://docs.livepeer.org/v1/api-reference/signing-key/get-all GET /access-control/signing-key # Overview Source: https://docs.livepeer.org/v1/api-reference/signing-key/overview The Access control API is used to create, retrieve, update, delete signing keys object from pipeline. ### Signing key Object Unique identifier for the signing key. Name of the signing key. Timestamp (in milliseconds) at which the signing key was created. Timestamp of the last activity with the signing key. The public key portion of the signing key. Indicates whether the signing key is disabled. The private key portion of the signing key. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "78df0075-b5f3-4683-a618-1086faca35dc", "name": "My signing key", "createdAt": 1587667174725, "lastSeen": 1587667174725, "publicKey": "LS0tLS1CRUdJTiBQUklWQVRFIBtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1RDRzhRWDZKdkR0eC95ZDMKdlpkUHJKR25LcjhiWHRsdXNIL2FOYW5XdHEraFJBTkNBQVE0QnZ6ODI2L2lDaXV1U0NiZVkwc3FmOXljYWh0OApDRFYyUFF2bDFVM1FLSVRBcWRpaktLa0FSUFVkcWRrYWZzR21PMzBDeElPaDBLNWJSQW5XQzd4KwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==", "disabled": "boolean", "privateKey": "LS0tLS1CRUdJTiBQUklWQVRFIBtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1RDRzhRWDZKdkR0eC95ZDMKdlpkUHJKR25LcjhiWHRsdXNIL2FOYW5XdHEraFJBTkNBQVE0QnZ6ODI2L2lDaXV1U0NiZVkwc3FmOXljYWh0OApDRFYyUFF2bDFVM1FLSVRBcWRpaktLa0FSUFVkcWRrYWZzR21PMzBDeElPaDBLNWJSQW5XQzd4KwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==" } ``` # Update a signing key Source: https://docs.livepeer.org/v1/api-reference/signing-key/update PATCH /access-control/signing-key/{keyId} # Add a multistream target Source: https://docs.livepeer.org/v1/api-reference/stream/add-multistream-target POST /stream/{id}/create-multistream-target # Create a livestream Source: https://docs.livepeer.org/v1/api-reference/stream/create POST /stream The only parameter you are required to set is the name of your stream, but we also highly recommend that you define transcoding profiles parameter that suits your specific broadcasting configuration. \ \ If you do not define transcoding rendition profiles when creating the stream, a default set of profiles will be used. These profiles include 240p, 360p, 480p and 720p. \ \ The playback policy is set to public by default for new streams. It can also be added upon the creation of a new stream by adding `"playbackPolicy": {"type": "jwt"}` # Create a clip Source: https://docs.livepeer.org/v1/api-reference/stream/create-clip POST /clip # Delete a livestream Source: https://docs.livepeer.org/v1/api-reference/stream/delete DELETE /stream/{id} This will also suspend any active stream sessions, so make sure to wait until the stream has finished. To explicitly interrupt an active session, consider instead updating the suspended field in the stream using the PATCH stream API. # Remove a multistream target Source: https://docs.livepeer.org/v1/api-reference/stream/delete-multistream-target DELETE /stream/{id}/multistream/{targetId} # Retrieve a livestream Source: https://docs.livepeer.org/v1/api-reference/stream/get GET /stream/{id} # Retrieve all livestreams Source: https://docs.livepeer.org/v1/api-reference/stream/get-all GET /stream # Retrieve clips of a livestream Source: https://docs.livepeer.org/v1/api-reference/stream/get-clip GET /stream/{id}/clips # Overview Source: https://docs.livepeer.org/v1/api-reference/stream/overview The Livestream API is used to create, retrieve, update, delete stream object from pipeline. ### Stream Object Unique identifier for the stream. Name of the stream. Reference to the creator-id schema. Timestamp of the last activity on the stream. Number of source segments. Number of transcoded segments. Duration of all the source segments in seconds. Duration of all the transcoded segments in seconds. Total bytes of source segments. Total bytes of transcoded segments. Rate at which sourceBytes increases (bytes/second). Rate at which transcodedBytes increases (bytes/second). Indicates if the stream is currently active. Reference to the stream-health-payload schema for health status. Reference to the stream-health-payload schema for human-readable issues. Name of the token used to create this stream. Timestamp (in milliseconds) of when the stream object was created. Identifier for the parent stream object. Key used to form the RTMP ingest URL. Identifier used to form the playback URL. Reference to the playback-policy schema. Array of profiles detailing various streaming qualities. Indicates if the stream should be recorded. Details about multistreaming targets and their configurations. Indicates if the stream is currently suspended. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "de7818e7-610a-4057-8f6f-b785dc1e6f88", "name": "test_stream", "creatorId": "object", "lastSeen": 1587667174725, "sourceSegments": 1, "transcodedSegments": 2, "sourceSegmentsDuration": 1, "transcodedSegmentsDuration": 2, "sourceBytes": 1, "transcodedBytes": 2, "ingestRate": 1, "outgoingRate": 2, "isActive": "boolean", "isHealthy": true, "issues": ["Buffer underflow", "Network congestion"], "createdByTokenName": "staging key", "createdAt": 1587667174725, "parentId": "de7818e7-610a-4057-8f6f-b785dc1e6f88", "streamKey": "hgebdhhigq", "playbackId": "eaw4nk06ts2d0mzb", "playbackPolicy": { "type": "public", "webhookId": "3e02c844-d364-4d48-b401-24b2773b5d6c", "webhookContext": { "foo": "bar" } }, "profiles": [ { "name": "720p", "width": 1280, "height": 720, "bitrate": 3000, "fps": 30 } ], "record": "boolean", "multistream": { "targets": [ { "id": "PUSH123", "profile": "720p" } ] }, "suspended": "boolean" } ``` # Terminates a livestream Source: https://docs.livepeer.org/v1/api-reference/stream/terminate DELETE /stream/{id}/terminate `DELETE /stream/{id}/terminate` can be used to terminate an ongoing session on a live stream. Unlike suspending the stream, it allows the streamer to restart streaming even immediately, but it will force terminate the current session and stop the recording. \ \ A 204 No Content status response indicates the stream was successfully terminated. # Update a livestream Source: https://docs.livepeer.org/v1/api-reference/stream/update PATCH /stream/{id} # Retrieve a task Source: https://docs.livepeer.org/v1/api-reference/task/get GET /task/{taskId} # Retrieve tasks Source: https://docs.livepeer.org/v1/api-reference/task/get-all GET /task # Overview Source: https://docs.livepeer.org/v1/api-reference/task/overview The Tasks API is used to retrieve tasks object from pipeline. ### Task Object Task ID. Type of the task, such as upload, import, export, etc. Timestamp (in milliseconds) at which the task was created. Timestamp (in milliseconds) at which the task was scheduled for execution. ID of the input asset for the task. ID of the output asset for the task. Parameters of the task, which vary depending on the task type. Status of the task, including phase, update time, and error message. Output of the task, specifics depend on the task type. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "type": "upload", "createdAt": 1587667174725, "scheduledAt": 1587667174725, "inputAssetId": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "outputAssetId": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "params": { "upload": { "url": "https://cdn.livepeer.com/ABC123/filename.mp4", "encryption": { "encryptedKey": "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1RDRzhRWDZKdkR0eC95ZDMKdlpkUHJKR25LcjhiWHRsdXNIL2FOYW5XdHEraFJBTkNBQVE0QnZ6ODI2L2lDaXV1U0NiZVkwc3FmOXljYWh0OApDRFYyUFF2bDFVM1FLSVRBcWRpaktLa0FSUFVkcWRrYWZzR21PMzBDeElPaDBLNWJSQW5XQzd4KwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==" }, "recordedSessionId": "78df0075-b5f3-4683-a618-1086faca35dc" }, "import": { "url": "https://cdn.livepeer.com/ABC123/filename.mp4", "encryption": { "encryptedKey": "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ1RDRzhRWDZKdkR0eC95ZDMKdlpkUHJKR25LcjhiWHRsdXNIL2FOYW5XdHEraFJBTkNBQVE0QnZ6ODI2L2lDaXV1U0NiZVkwc3FmOXljYWh0OApDRFYyUFF2bDFVM1FLSVRBcWRpaktLa0FSUFVkcWRrYWZzR21PMzBDeElPaDBLNWJSQW5XQzd4KwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==" }, "recordedSessionId": "78df0075-b5f3-4683-a618-1086faca35dc" }, "exportData": { "content": "object", "ipfs": { "nftMetadataTemplate": "file", "nftMetadata": "object", "pinata": { "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDI4NjQwNzcsImlhdCI6MTYwMjI3NjA3NywiaXNzIjoiYXBpLmlzc3VlciIsInN1YiI6I" } }, "type": "string", "id": "string" }, "transcode": { "profile": { "width": 1280, "name": "720p", "height": 720, "bitrate": 4000, "fps": 30, "fpsDen": 1, "gop": "60", "profile": "H264High", "encoder": "h264" } }, "transcode-file": { "input": { "url": "https://cdn.livepeer.com/ABC123/filename.mp4" }, "storage": { "url": "s3+https://accessKeyId:secretAccessKey@s3Endpoint/bucket" }, "outputs": { "hls": { "path": "/samplevideo/hls" }, "mp4": { "path": "/samplevideo/mp4" } }, "profiles": [ { "width": 1280, "name": "720p", "height": 720, "bitrate": 4000, "fps": 30, "fpsDen": 1, "gop": "60", "profile": "H264High", "encoder": "h264" } ], "targetSegmentSizeSecs": "number", "c2pa": "boolean" } }, "clip": { "url": "string", "clipStrategy": { "startTime": "number", "endTime": "number", "playbackId": "string" }, "catalystPipelineStrategy": "catalyst", "sessionId": "string", "inputId": "string" }, "status": { "phase": "pending", "updatedAt": 1587667174725, "progress": "number", "errorMessage": "string", "retries": "number", "step": "string" }, "output": { "upload": { "videoFilePath": "string", "metadataFilePath": "string", "assetSpec": { "id": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "type": "video", "playbackId": "eaw4nk06ts2d0mzb", "staticMp4": "boolean", "c2pa": "boolean", "playbackUrl": "https://livepeercdn.com/asset/ea03f37e-f861-4cdd-b495-0e60b6d753ad/index.m3u8", "downloadUrl": "https://livepeercdn.com/asset/eaw4nk06ts2d0mzb/video", "playbackPolicy": { "type": "public", "webhookId": "3e02c844-d364-4d48-b401-24b2773b5d6c", "webhookContext": { "foo": "bar" } }, "source": { "type": "url", "url": "https://example.com/video.mp4", "gatewayUrl": "https://example.com/video.mp4" }, "creatorId": "object", "storage": { "ipfs": { "spec": { "nftMetadataTemplate": "file", "nftMetadata": { "name": "My NFT", "description": "My NFT description" } } } }, "status": { "phase": "ready", "updatedAt": 1587667174725, "progress": 100 }, "name": "filename.mp4", "createdAt": 1587667174725, "size": 84934509, "hash": [ { "hash": "9b560b28b85378a5004117539196ab24e21bbd75b0e9eb1a8bc7c5fd80dc5b57", "algorithm": "sha256" } ], "videoSpec": { "format": "mp4", "duration": 23.8328, "bitrate": 1000000, "tracks": [ { "type": "video", "codec": "h264", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "width": 1920, "height": 1080, "pixelFormat": "yuv420p", "fps": 30 }, { "type": "audio", "codec": "aac", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "channels": 2, "sampleRate": 44100, "bitDepth": 16 } ] } } }, "import": { "videoFilePath": "string", "metadataFilePath": "string", "assetSpec": { "id": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "type": "video", "playbackId": "eaw4nk06ts2d0mzb", "staticMp4": "boolean", "c2pa": "boolean", "playbackUrl": "https://livepeercdn.com/asset/ea03f37e-f861-4cdd-b495-0e60b6d753ad/index.m3u8", "downloadUrl": "https://livepeercdn.com/asset/eaw4nk06ts2d0mzb/video", "playbackPolicy": { "type": "public", "webhookId": "3e02c844-d364-4d48-b401-24b2773b5d6c", "webhookContext": { "foo": "bar" } }, "source": { "type": "url", "url": "https://example.com/video.mp4", "gatewayUrl": "https://example.com/video.mp4" }, "creatorId": "object", "storage": { "ipfs": { "spec": { "nftMetadataTemplate": "file", "nftMetadata": { "name": "My NFT", "description": "My NFT description" } } } }, "status": { "phase": "ready", "updatedAt": 1587667174725, "progress": 100 }, "name": "filename.mp4", "createdAt": 1587667174725, "size": 84934509, "hash": [ { "hash": "9b560b28b85378a5004117539196ab24e21bbd75b0e9eb1a8bc7c5fd80dc5b57", "algorithm": "sha256" } ], "videoSpec": { "format": "mp4", "duration": 23.8328, "bitrate": 1000000, "tracks": [ { "type": "video", "codec": "h264", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "width": 1920, "height": 1080, "pixelFormat": "yuv420p", "fps": 30 }, { "type": "audio", "codec": "aac", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "channels": 2, "sampleRate": 44100, "bitDepth": 16 } ] } } }, "export": { "ipfs": { "videoFileCid": "string", "videoFileUrl": "string", "videoFileGatewayUrl": "string", "nftMetadataCid": "string", "nftMetadataUrl": "string", "nftMetadataGatewayUrl": "string" } }, "exportData": { "ipfs": { "cid": "string" } }, "transcode": { "asset": { "videoFilePath": "string", "metadataFilePath": "string", "assetSpec": { "id": "09F8B46C-61A0-4254-9875-F71F4C605BC7", "type": "video", "playbackId": "eaw4nk06ts2d0mzb", "staticMp4": "boolean", "c2pa": "boolean", "playbackUrl": "https://livepeercdn.com/asset/ea03f37e-f861-4cdd-b495-0e60b6d753ad/index.m3u8", "downloadUrl": "https://livepeercdn.com/asset/eaw4nk06ts2d0mzb/video", "playbackPolicy": { "type": "public", "webhookId": "3e02c844-d364-4d48-b401-24b2773b5d6c", "webhookContext": { "foo": "bar" } }, "source": { "type": "url", "url": "https://example.com/video.mp4", "gatewayUrl": "https://example.com/video.mp4" }, "creatorId": "object", "storage": { "ipfs": { "spec": { "nftMetadataTemplate": "file", "nftMetadata": { "name": "My NFT", "description": "My NFT description" } } } }, "status": { "phase": "ready", "updatedAt": 1587667174725, "progress": 100 }, "name": "filename.mp4", "createdAt": 1587667174725, "size": 84934509, "hash": [ { "hash": "9b560b28b85378a5004117539196ab24e21bbd75b0e9eb1a8bc7c5fd80dc5b57", "algorithm": "sha256" } ], "videoSpec": { "format": "mp4", "duration": 23.8328, "bitrate": 1000000, "tracks": [ { "type": "video", "codec": "h264", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "width": 1920, "height": 1080, "pixelFormat": "yuv420p", "fps": 30 }, { "type": "audio", "codec": "aac", "startTime": 0, "duration": 23.8328, "bitrate": 1000000, "channels": 2, "sampleRate": 44100, "bitDepth": 16 } ] } } } } } } ``` # Create a webhook Source: https://docs.livepeer.org/v1/api-reference/webhook/create POST /webhook To create a new webhook, you need to make an API call with the events you want to listen for and the URL that will be called when those events occur. # Delete a webhook Source: https://docs.livepeer.org/v1/api-reference/webhook/delete DELETE /webhook/{id} # Retrieve a webhook Source: https://docs.livepeer.org/v1/api-reference/webhook/get GET /webhook/{id} # Retrieve all webhooks Source: https://docs.livepeer.org/v1/api-reference/webhook/get-all GET /webhook # Overview Source: https://docs.livepeer.org/v1/api-reference/webhook/overview The Webhooks API is used to create, retrieve, update, delete webhooks object from pipeline. ### Webhook Object Unique identifier for the webhook. Name of the webhook. Timestamp (in milliseconds) at which the webhook object was created. List of events that the webhook subscribes to. Possible events include stream.started, stream.detection, recording.ready, etc. URL of the webhook endpoint. Shared secret used to sign the webhook payload. StreamId of the stream to which the webhook is applied. Status of the webhook, including last failure and last triggered timestamp. Details about the last failure of the webhook, including timestamp, error message, response, and status code. Timestamp (in milliseconds) at which the webhook was last triggered. ```json Response theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "id": "de7818e7-610a-4057-8f6f-b785dc1e6f88", "name": "My webhook", "createdAt": 1587667174725, "events": [ "stream.started", "recording.ready" ], "url": "https://webhook.example.com", "sharedSecret": "mySharedSecret", "streamId": "de7818e7-610a-4057-8f6f-b785dc1e6f88", "status": { "lastFailure": { "timestamp": 1587667174725, "error": "Error message", "response": "Response body", "statusCode": 500 }, "lastTriggeredAt": 1587667174725 } } ``` # Update a webhook Source: https://docs.livepeer.org/v1/api-reference/webhook/update PUT /webhook/{id} # Control access using JWTs Source: https://docs.livepeer.org/v1/developers/guides/access-control-jwt Learn how to add access control to a content with Livepeer UI Kit, using JWTs Using JSON Web Tokens (JWTs) provides a robust way to control access to both your assets and livestreams. The JWTs can be signed and validated to ensure that only authorized users can access the content. Below are examples for both assets and livestreams. Adding access control to a content only takes a few lines of code. This guide is written for developers using `@livepeer/react` in a React application. ## Create Gated Content ### For livestreams Create your gated stream, with the stream key returned once we create it (styling has been removed for simplicity) ```tsx accessControl.tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; import { TypeT } from "livepeer/dist/models/components"; const livepeer = new Livepeer({ apiKey: process.env.STUDIO_API_KEY ?? "", }); await livepeer.stream.create({ name: "...", playbackPolicy: { type: TypeT.Jwt, }, }); ``` ### For assets Create your gated asset, with the jwt playback policy type. ```tsx accessControl.ts theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; import { TypeT } from "livepeer/dist/models/components"; const livepeer = new Livepeer({ apiKey: process.env.STUDIO_API_KEY ?? "", }); await livepeer.asset.create({ name: "...", playbackPolicy: { type: TypeT.Jwt, }, }); ``` ## Sign a JWT (Node.JS API Route) Next, we add an API route - since we are using Next.JS, we add a custom [Next.js API route](https://nextjs.org/docs/api-routes/introduction). We add a check in the API route for a special "secret" that must be passed in the POST body for the user to gain access to the stream. Make sure to [create a signing key](/api-reference/signing-key/create) - those values will be used as the environment variables `ACCESS_CONTROL_PRIVATE_KEY` and `NEXT_PUBLIC_ACCESS_CONTROL_PUBLIC_KEY`. ```typescript /api/sign-jwt.ts theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { signAccessJwt } from "@livepeer/core/crypto"; import { NextApiRequest, NextApiResponse } from "next"; import { ApiError } from "../../lib/error"; export type CreateSignedPlaybackBody = { playbackId: string; secret: string; }; export type CreateSignedPlaybackResponse = { token: string; }; const accessControlPrivateKey = process.env.ACCESS_CONTROL_PRIVATE_KEY; const accessControlPublicKey = process.env.NEXT_PUBLIC_ACCESS_CONTROL_PUBLIC_KEY; const handler = async ( req: NextApiRequest, res: NextApiResponse ) => { try { const method = req.method; if (method === "POST") { if (!accessControlPrivateKey || !accessControlPublicKey) { return res .status(500) .json({ message: "No private/public key configured." }); } const { playbackId, secret }: CreateSignedPlaybackBody = req.body; if (!playbackId || !secret) { return res.status(400).json({ message: "Missing data in body." }); } // we check that the "supersecretkey" was passed in the body // this could be a more complex check, like taking a signed payload, // getting the address for that signature, and fetching if they own an NFT // // https://docs.ethers.io/v5/single-page/#/v5/api/utils/signing-key/-%23-SigningKey--other-functions if (secret !== "supersecretkey") { return res.status(401).json({ message: "Incorrect secret." }); } // we sign the JWT and return it to the user const token = await signAccessJwt({ privateKey: accessControlPrivateKey, publicKey: accessControlPublicKey, issuer: "https://docs.livepeer.org", // playback ID to include in the JWT playbackId, // expire the JWT in 1 hour expiration: "1h", // custom metadata to include custom: { userId: "user-id-1", }, }); return res.status(200).json({ token, }); } res.setHeader("Allow", ["POST"]); return res.status(405).end(`Method ${method} Not Allowed`); } catch (err) { console.error(err); return res .status(500) .json({ message: (err as Error)?.message ?? "Error" }); } }; export default handler; ``` ### Configure the Player Lastly, when the content is created, we make a POST request to the `/api/create-signed-jwt` API route we created in the previous step. The React Player passes the JWT with a header, `Livepeer-Jwt`, to the backend, for WebRTC and HLS playback. For MP4 playback, it uses a query parameter, `jwt`. Then, we pass the JWT to the Player using the [`jwt`](/sdks/react/player/Root#jwt) prop, which will use that JWT to prove access to the content! ```tsx access-control.tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} import * as Player from "@livepeer/react/player"; export const AccessControl = () => { return ( ); }; ``` ### Using a custom player If you are not using the player, you will need to pass a header, `Livepeer-Jwt`, when you perform WebRTC SDP negotiation, or when you play back from a m3u8 URL. For WebRTC SDP negotiation, here is an example of the header being passed: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X POST \ -H "Content-Type: application/sdp" \ -H "Livepeer-Jwt: your-jwt" \ --data-binary "@sdpfile.sdp" \ "https://livepeercdn.studio/webrtc/abcd1234" ``` You can also append the JWT to the WebRTC URL as a query parameter, similar to: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X POST \ -H "Content-Type: application/sdp" \ --data-binary "@sdpfile.sdp" \ "https://livepeercdn.studio/webrtc/abcd1234?jwt=your-jwt" ``` Similarly, for HLS playback, you can pass the JWT in a header: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X GET \ -H "Livepeer-Jwt: your-jwt" \ "https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8" ``` If you are using HLS.js for your own custom player, you can set the JWT header like this: ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} const hlsConfig = { xhrSetup: function (xhr, url) { xhr.setRequestHeader("Livepeer-Jwt", "your-jwt"); }, }; ``` Finally, you can append the JWT to the m3u8 URL as a query parameter: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X GET \ "https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8?jwt=your-jwt" ``` # Control access using webhooks Source: https://docs.livepeer.org/v1/developers/guides/access-control-webhooks Learn how to add access control to a content with React, using webhooks Webhooks offer a versatile and dynamic approach to access control for both assets and livestreams. By setting up a webhook, you can validate access requests on the fly, allowing for real-time, context-sensitive decisions. When a user requests access to content, the webhook you've specified will be called. This webhook can then decide whether to grant or deny access based on custom logic, such as user authentication, subscription status, or any other criteria. This guide is written for developers using `@livepeer/react` in a React application. The example below uses webhook to create and watch a gated content. ## Create an Access Control Webhook Handler Set up an endpoint (e.g., [https://yourdomain.com/api/check-access](https://yourdomain.com/api/check-access)) to handle the logic for granting or denying access to your assets. This endpoint should accept a POST request with a JSON payload containing the access key and webhook context. This is an example of a payload this endpoint would receive: ```json POST /api/check-access theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "accessKey": "your-access-key", "context": { "assetId": "abcd1234", "userId": "user5678" }, "timestamp": 1680530722502 } ``` The payload in `context` is defined by your application. ### Register the Access Control Webhook Use the [Livepeer Studio dashboard](https://livepeer.studio/dashboard/developers/webhooks) to create a new webhook with the type `playback.accessControl` and specify the URL of your access control endpoint. You can then use the ID of the webhook in the next step. ## Create content with a playback policy webhook ### For assets Create an asset with a playback policy webhook, passing the ID of the webhook you created in the previous step. ```tsx accessControl.ts theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; import { TypeT } from "livepeer/dist/models/components"; const livepeer = new Livepeer({ apiKey: process.env.STUDIO_API_KEY ?? "", }); await livepeer.asset.create({ name: "...", playbackPolicy: { type: TypeT.Webhook, // This is the id of the webhook you created in step 2 webhookId: "", webhookContext: { // This is the context you want to pass to your webhook // It can be anything you want, and it will be passed back to your webhook }, }, }); ``` You can then use the [returned TUS URL](/api-reference/asset/upload) to upload the asset. ### For livestreams Create a stream with a playback policy webhook, passing the ID of the webhook you created in the previous step. ```tsx accessControl.ts theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; import { TypeT } from "livepeer/dist/models/components"; const livepeer = new Livepeer({ apiKey: process.env.STUDIO_API_KEY ?? "", }); await livepeer.stream.create({ name: "...", playbackPolicy: { type: TypeT.Webhook, // This is the id of the webhook you created in step 2 webhookId: "", webhookContext: { // This is the context you want to pass to your webhook // It can be anything you want, and it will be passed back to your webhook }, }, }); ``` ### Configure the Player If you are using the Livepeer UI Kit Player, you can use the [`accessKey`](/sdks/react/player/Root#accesskey) prop to provide your custom access key, which is then passed to the webhook you created to verify that it is valid. The React Player passes the access key with a header, `Livepeer-Access-Key`, to the backend, for WebRTC and HLS playback. For MP4 playback, it uses a query parameter, `accessKey`. ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} import * as Player from "@livepeer/react/player"; export const CreateAndViewAsset = () => { const accessKey = getAccessKeyForYourApplication(); return ( ); }; ``` ### Using a custom player If you are not using the player, you will need to pass a header, `Livepeer-Access-Key`, when you perform WebRTC SDP negotiation, or when you play back from a m3u8 URL. For WebRTC SDP negotiation, here is an example of the header being passed: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X POST \ -H "Content-Type: application/sdp" \ -H "Livepeer-Access-Key: your-access-key" \ --data-binary "@sdpfile.sdp" \ "https://livepeercdn.studio/webrtc/abcd1234" ``` You can also append the access key to the WebRTC URL as a query parameter, similar to: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X POST \ -H "Content-Type: application/sdp" \ --data-binary "@sdpfile.sdp" \ "https://livepeercdn.studio/webrtc/abcd1234?accessKey=your-access-key" ``` Similarly, for HLS playback, you can pass the access key in a header: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X GET \ -H "Livepeer-Access-Key: your-access-key" \ "https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8" ``` If you are using HLS.js for your own custom player, you can set the access key header like this: ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} const hlsConfig = { xhrSetup: function (xhr, url) { xhr.setRequestHeader("Livepeer-Access-Key", "your-access-key"); }, }; ``` Finally, you can append the access key to the m3u8 URL as a query parameter: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -X GET \ "https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8?accessKey=your-access-key" ``` ### Receive the Webhook from Livepeer When a user attempts to access the content, Livepeer will call your access control endpoint with the access key and webhook context. Your endpoint should process the request and return a response indicating whether the stream access should be allowed or disallowed. Here is an example request sent to your access control endpoint: ```json theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "context": { // Same value from the `asset.playbackPolicy.webhookContext` field }, "accessKey": "" // this is exact value of the prop passed to the Player, or the query parameter } ``` In your access control endpoint, implement the logic to verify the access key and decide whether to grant access to the content. If the access is allowed, return a `2XX` response. Otherwise, the playback will be disallowed. # Clip a livestream Source: https://docs.livepeer.org/v1/developers/guides/clip-a-livestream Learn how to create clips from your livestream ## Introduction You can use Livepeer Studio to create clips of active streams provided via API. Currently, you can create clips from the most recent N seconds of a given stream. You may also clip specific sections of a long-running stream, such as a particular session from a live-streamed conference. This guide offers a comprehensive walkthrough of the clipping functionality. Clipping is currently *only* supported by livestreams and livestream recordings. Check back later for further support. ## Create a stream Follow our previous guide on [creating a stream](/developers/guides/create-livestream) to get a stream key to provide to the creator on your platform. They can then start broadcasting to that stream. ## Create a clip Clips are created from the perspective of the user who initiates the clip. For example, if a viewer clips the most recent 30 seconds, it will be the most recent 30 seconds that they saw. Submit a request to the [Create Clip API](/api-reference/stream/create-clip) using the following request parameters: UNIX timestamp (in milliseconds) of clip's starts from the browser’s playhead (commonly supplied by HLS players). UNIX timestamp (in milliseconds) of clip's end from the browser’s playhead (commonly supplied by HLS players). Active stream's Playback ID (Optional) Output clip's name Ensure the `startTime` aligns with or postdates the stream's initiation, and that the `endTime` isn't set in the future. ### API + Player Integration If you are using HLS.js, it provides an API for getting the program date time, `hls.playingDate`, which can be used to get the browser’s current playhead. This returns a Date object which is used similar to: HLS players typically provide a `Program-Date-Time` for each segment when parsing an HLS manifest. You can utilize `Program-Date-Time` to create a clipping user interface (UI) and generate the correct timestamps. ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} import Hls from "hls.js"; const hls = new Hls(); // Assuming you have already connected HLS.js to a video element const endTime = hls.playingDate.getTime(); // This retrieves the current playing date from HLS.js const startTime = endTime - 30000; // 30 seconds before the endTime in milliseconds const playbackId = process.env.PLAYBACK_ID_OF_RUNNING_STREAM; const result = await livepeer.stream.createClip({ startTime, endTime, playbackId, name: "Your clip name", }); ``` If you are using the Livepeer Player, you use the [Clip Trigger](/sdks/react/player/Clip), which abstracts this functionality into an easy-to-use primitive. **You will need to call the Livepeer Clip API from your backend, and perform sanitization on the user input.** ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} { // use the start/end time to call the Livepeer API, from your backend // e.g. this should be a fetch to your /api/clip endpoint, which then // makes a request to Livepeer's Clip endpoint, after validating the // input parameters (e.g. clip is less than 60 seconds, etc) }} > ``` ## Monitor the clip's status After calling the clip API, Livepeer generates an asset. You'll receive the asset's details in the response. ```json theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "task": { "id": "635cbcbb-30cb-4136-a7f0-ec5ea2ac39d0" }, "asset": { "id": "e28c63c6-ffe8-4f0f-8ae0-4fbbe5c7b493", "playbackId": "e28crdyrtxjkx836", "userId": "8f175616-1a6d-4481-86f5-7351c041b5ca", "createdAt": 1695835443441, "status": { "phase": "waiting", "updatedAt": 1695835443441 }, "name": "My Clip", "source": { "type": "clip", "sessionId": "e0d88141-8ade-4274-9039-360245283645" }, "objectStoreId": "9b526cee-bd25-44d2-9fda-f98db3a38a48" } } ``` There are two approaches to track the clip's status: * **using the `/api/asset/$ASSET_ID` API** You can poll this endpoint to retrieve the current status of the asset. Keep polling until the `asset.status` field is `ready`. * **using webhooks** If you have configured a webhook in Livepeer to listen for the `asset.ready` event, you will receive a notification when the clip processing is complete. You can use the `assetId` received in the previous response to determine when your clip is ready. To determine if an asset you are polling or receiving as an event is a clip, you can check the `source` field. If the `source` field contains a `clip` type and the relative session ID, then it is indeed a clip. You can always fetch clips by stream using the [Clips for Livestream API](/api-reference/stream/get-clip): ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} const playbackId = process.env.PLAYBACK_ID_OF_RUNNING_STREAM; const livepeer = new Livepeer({ apiKey: "your_api_key_here", }); const id = "08eeeda4-681f-46e5-a760-63d6cac91d91"; await livepeer.stream.getAllClips(id); // Alternatively, you can use the streamId or fetch the clips // by session using the /api/session/$sessionId/clips endpoint. ``` Clips offer source playback just like other assets. On completion, a transcode job is triggered. Even before this job concludes, the source playback becomes available. The `playbackUrl` field in the asset gets populated when the playback is ready, and the `sourcePlayback` field is set to true. ## Get your clip! A clip generated in Livepeer is the same as any other Livepeer asset. You are able to get the playback URLs in your asset object, or fetching them from the [Playback Info API](/api-reference/playback/get), providing the playbackId of the output asset representing the clip. # Create a livestream Source: https://docs.livepeer.org/v1/developers/guides/create-livestream Learn how to create a livestream Creating and watching a livestream is easy! The example below uses [Create Stream API](/api-reference/stream/create) to create and watch a livestream. ### Stream Creation We can use the Livepeer SDK to create a stream. The example below uses the [Create Stream API](/api-reference/stream/create). ```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; const apiKey = 'YOUR_API_KEY'; const livepeer = new Livepeer({apiKey}); const streamData = { name: "test_stream" }; livepeer .stream.create(streamData) .then((response) => { console.log("Stream created:", response); }) .catch((error) => { console.error("Error creating stream:", error); }); ``` ```python theme={"theme":{"light":"github-light","dark":"dark-plus"}} from livepeer import Livepeer # Initialize the Livepeer client with your API key api_key = "YOUR_API_KEY" livepeer = Livepeer(api_key) stream_data = { "name": "test_stream" } try: # Create the stream response = livepeer.stream.create(stream_data) print("Stream created:", response) except Exception as e: print("Error creating stream:", e) ``` ```go theme={"theme":{"light":"github-light","dark":"dark-plus"}} package main import ( "fmt" "os" "github.com/livepeer/livepeer-go" ) func main() { // Initialize the Livepeer client with your API key apiKey := "YOUR_API_KEY" client := livepeer.NewLivepeerClient(apiKey) streamData := map[string]interface{}{ "name": "test_stream", } // Create the stream response, err := client.Stream.Create(streamData) if err != nil { fmt.Printf("Error creating stream: %v\n", err) os.Exit(1) } fmt.Printf("Stream created: %+v\n", response) } ``` You can find all the information required to broadcast to the stream in the [response object](/api-reference/stream/overview). The RTMP ingest URL of Livepeer Studio is `rtmp://rtmp.livepeer.com/live`, and the `streamKey` is present in the response object. You can use these two values to broadcast to the stream. You can also use WebRTC WHIP to broadcast, using the URL `https://playback.livepeer.studio/webrtc/{streamKey}`. This is used for [in-browser broadcasting](/developers/guides/livestream-from-browser). To learn more about other stream functions such as stopping a stream, recording a stream, and more, see the [Stream API](/api-reference/stream/overview). ### Play a Stream To learn how to play a stream, see the [Play a Livestream](/developers/guides/playback-a-livestream) guide. # Upload encrypted assets Source: https://docs.livepeer.org/v1/developers/guides/encrypted-asset Learn how to upload and play back an encrypted video asset Livepeer enables you to upload encrypted video content or import it from decentralized storages. It creates a playback URL that can have independent access control through Livepeer's [access control](/developers/guides/overview#access-control) feature. This guide is written for developers using JavaScript, but the concepts can be applied to any language. ### Generate an encryption key First, we generate a 256-bit encryption key to encrypt the file. ```js theme={"theme":{"light":"github-light","dark":"dark-plus"}} // Generate a random 256-bit key const key = await window.crypto.subtle.generateKey( { name: "AES-CBC", length: 256, }, true, ["encrypt", "decrypt"] ); // Export the key as raw data const keyData = await window.crypto.subtle.exportKey("raw", key); // Encode the key in Base64 const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(keyData))); ``` ### Encrypt your video To encrypt a video file with the key we just generated, we follow these steps: 1. Generate a random 128-bit initialization vector (IV). 2. Pad the data using PKCS#7 padding. 3. Encrypt the data using AES-CBC. ```js theme={"theme":{"light":"github-light","dark":"dark-plus"}} const iv = window.crypto.getRandomValues(new Uint8Array(16)); const encrypted = await window.crypto.subtle.encrypt( { name: "AES-CBC", iv: iv, }, key, // from generateKey or importKey above arrayBuffer // ArrayBuffer of data you want to encrypt ); // Concatenate IV and encrypted file into a new ArrayBuffer const resultBuffer = new ArrayBuffer(iv.length + encrypted.byteLength); new Uint8Array(resultBuffer).set(new Uint8Array(iv), 0); new Uint8Array(resultBuffer).set(new Uint8Array(encrypted), iv.length); const blob = new Blob([resultBuffer], { type: "application/octet-stream" }); ``` Currently Livepeer supports only video content encrypted using [SubtleCrypto.encrypt](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt) with `AES-CBC` algorithm, which is also the encryption used by other web3 protocols like [Lit](https://developer.litprotocol.com/sdk/explanation/encryption/). It can implemented in other environments with regular AES-CBC encryption using PKCS#7 padding. ### Retrieve the Livepeer Public Key After obtaining an encryption key and encrypting a video file with it, the next step is to retrieve the Livepeer Public Key. This key will be used to encrypt our encryption key, which can then be shared with Livepeer when creating a video asset. ```js theme={"theme":{"light":"github-light","dark":"dark-plus"}} // Fetch the public key from Livepeer const publicKeyResponse = await fetch( "https://livepeer.studio/api/access-control/public-key", { headers: { Authorization: "Bearer XXXX-XXXXXX-XXXXXXX-XXXX", }, } ); const publicKeyData = await publicKeyResponse.json(); const publicKey = publicKeyData.spki_public_key; ``` ### Encrypt the encryption key Now, we can use asymmetric encryption with the Livepeer Public Key to encrypt our Encryption Key. The resulting data is then encoded in base64 before being sent to Livepeer along with the video file. The Web Cryptography API built into modern browsers does not directly support PEM or PKCS#1 formatted keys. It only supports the SPKI format for public keys and the PKCS8 format for private keys. In Javascript, you need to use the `spki_public_key`. ```js theme={"theme":{"light":"github-light","dark":"dark-plus"}} // Decode the SPKI public key from base64 and convert it to a buffer const spkiPublicKey = atob(publicKeyData.spki_public_key); const publicKeyBuffer = Uint8Array.from(atob(spkiPublicKey), (c) => c.charCodeAt(0) ).buffer; // Import the public key const publicKey = await window.crypto.subtle.importKey( "spki", publicKeyBuffer, { name: "RSA-OAEP", hash: { name: "SHA-256" }, }, false, ["encrypt"] ); // Encrypt the key data with the public key const encryptedKeyData = await window.crypto.subtle.encrypt( { name: "RSA-OAEP", }, publicKey, keyData ); // Base64 encode the encrypted key data const encryptedKeyBase64 = btoa( String.fromCharCode(...new Uint8Array(encryptedKeyData)) ); ``` ### Upload/import the video file When requesting an upload to the Livepeer API, you can simply [add an `encryption` field](/api-reference/asset/upload-via-url) with your encrypted key. When uploading an encrypted video, specifying a `playbackPolicy` is required. You can still specify the type of the playback policy as `public`, but be aware that anyone with the `playbackId` or `playbackUrl` would be able to watch it. ```js theme={"theme":{"light":"github-light","dark":"dark-plus"}} // Request the upload URL const response = await fetch( "https://livepeer.studio/api/asset/request-upload", { method: "POST", headers: { Authorization: "Bearer XXXX-XXXXXX-XXXXXX-XXXX", "Content-Type": "application/json", }, body: JSON.stringify({ name: "File name", encryption: { encryptedKey: encryptedKeyBase64, }, playbackPolicy: { // Your playback policy }, }), } ); if (!response.ok) { alert("Error requesting upload URL"); return; } const data = await response.json(); // Upload the encrypted file to the returned URL const uploadResponse = await fetch(data.url, { method: "PUT", body: encryptedFile, }); ``` # Engagement via API Source: https://docs.livepeer.org/v1/developers/guides/get-engagement-analytics-via-api Learn how to check out viewer engagement on Livepeer Livepeer offers detailed information on viewer behavior and playback quality on your platform. The API includes engagement metrics such as view counts and watch time, as well as performance metrics such as error rate, time to first frame, rebuffer ratio, and exit-before-starts across a variety of dimensions. ### Realtime Viewership Please note that in order to use Realtime Viewership metrics, you need to use Player SDK `@livepeer/react` version 4.2.0 or higher. This API includes real-time metrics about the ongoing livestream or VOD engagement: * Viewer Count: The number of viewers currently watching the stream (or VOD). * Error Rate: This metric helps to understand the number of errors that viewers are experiencing while watching the stream (or VOD). ### Usage Metrics This API includes usage metrics to help you understand how viewers are engaging with your platform: * Number of Views: This metric gives you a comprehensive view of the total number of views across your platform. We colloquially define a view as “play intent”, which means the video either played, stalled, or encountered an error. * Minutes of Watch Time: Measure the total amount of time viewers spend on your platform, so you can track engagement and identify areas for improvement. Please note that data is refreshed every 5 minutes and newly uploaded videos may take up to an hour before viewership data is available ### Performance Metrics This API offers several key performance metrics to help you understand your platforms performance: * Error Rate: Percentage of views that encountered an error. * Time to First Frame (TTFF): Measures the time in milliseconds it takes between the player requesting video and the first frame being ready to play. * Rebuffer Ratio: Percentage of time a viewer spends experiencing rebuffering during playback. * Exit Before Starts: Percentage of views that are abandoned before the video begins to play, potentially indicating issues with playback. Please note that data is refreshed every 5 minutes and newly uploaded videos may take up to an hour before viewership data is available ### Dimensions To provide a comprehensive view of viewer behavior, our data product includes several dimensions to help you segment your data and identify patterns: * Video: View metrics for individual videos, queryable by playback ID or by dStorage ID’s, such as CID. * Browser: Understand how different browsers impact viewer behavior and performance, so you can optimize your platform for each browser type. * Device: Understand how different devices impact viewer behavior, so you can optimize your platform for each device type. * OS: Understand how different operating systems impact viewer behavior, so you can optimize your platform for each OS. * Continent, Country, and Subdivisions: Segment your data by location to identify regional trends and tailor your platform to local preferences. * Time Zone: Measure viewer behavior across different time zones, so you can optimize your platform for peak usage times. * Time: Analyze viewer behavior across different time periods (hour, day, week, month, year, all-time) to identify trends. ## Registering views To collect and register viewership metrics, you first need to configure your player. We recommend that you use the [Livepeer player](/sdks/react/player/Root) to get viewership metrics, as it comes fully configured. You can follow the Player guide to get started. We also support viewership metrics for applications using custom players. In order for metrics to be tracked by Livepeer, you will need to configure your player using `addMediaMetrics`. Here's how to configure your player: Make sure you have initialized your player before calling `addMediaMetrics`. ```js theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { addMediaMetrics } from "@livepeer/core-web/browser"; const video = document.getElementById("my-video"); const { destroy } = addMediaMetrics(ref.current); // or, you can *optionally* pass options to the addMediaMetrics function const { destroy } = addMediaMetrics(ref.current, { // specify the source URL of the video, which should include a playbackId. // this should only be used when using a custom video player that sets a `blob:...` URL or no `src` on the video element. src: "https://livepeercdn.studio/hls/{playbackId}/index.m3u8", // specify a unique viewer ID for tracking purposes viewerId: "user123", // monitor when the metrics fails to report onError: (error) => { console.error("Metrics collection error:", error); }, }); ``` Make sure you have initialized your player before calling `addMediaMetrics`. ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { addMediaMetrics } from "@livepeer/core-web/media/browser"; import React, { useEffect, useRef, useState } from "react"; const source = "https://livepeercdn.studio/hls/{playbackId}/index.m3u8"; export default function VideoPlayer() { const ref = useRef(null); useEffect(() => { const { destroy } = addMediaMetrics(ref.current, { // specify the source URL of the video, which should include a playbackId. // this should only be used when using a custom video player that sets a `blob:...` URL or no `src` on the video element. src: source, // specify a unique viewer ID for tracking purposes viewerId: "user123", // monitor when the metrics fails to report onError: (error) => { console.error("Metrics collection error:", error); }, }); return () => destroy(); }, []); return ( ## Retrieving views from the Dashboard The Livepeer Studio Dashboard is a frontend interface for publishing live or on-demand video streams with no code. In this guide, we'll show you how to use the dashboard to retrieve viewership metrics. ### Navigate to the assets page Assets page ### Click on an existing asset Click on an existing asset and you'll be brought to that asset's specific details page. If you haven't created an asset yet, you can follow the [upload a video asset guide](/developers/guides/upload-video-asset). Assets page ### View your asset's viewership metrics In the asset's specific detail page you can view its total number of views. Asset views ## Retrieving views from the API ### Get the `playbackId` of an existing stream/asset Get the `playbackId` of an existing asset. A `playbackId` can be found in the stream/asset page on the dashboard or from any Asset API call. If you haven't created a stream/asset yet, you can follow the [upload a video asset guide](/developers/guides/upload-video-asset). The `playbackId` can be a canonical playback ID from a specific Livepeer asset or stream objects, or dStorage identifiers for assets. Queries by dStorage ID are universal/cross-account, as explained below, so check what makes the most sense for your app. When querying by dStorage ID (e.g. `ipfs://` or `ar://` URLs, or CID/txID), you will get views for all assets with that dStorage ID across any Livepeer account. This is useful to display global metrics from a video. To display the viewership metrics only from the videos in your account, use the API objects `playbackId`. ### Retrieve viewership data Once you have the `playbackId`, you can make a request to get the viewership data. ```sh theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl --request GET \ --url https://livepeer.studio/api/data/views/query/total/{playbackId} \ --header 'Authorization: Bearer ' ``` For more information on the API and SDK methods, refer to the [API reference](/api-reference/viewership/get-public-total-views). ### Diving deeper If you're happy with the above metrics you can stop here. If you want to build more advanced analytics, you can use the following API endpoints to get more detailed data. #### [Realtime Viewership API](/api-reference/viewership/get-realtime-viewership) This API can be called from the frontend to retrieve realtime engagement metrics (current view count and error rate). You can break down the results by playback id, browser, device, and country. ```sh theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl --request GET \ --url 'https://livepeer.studio/api/data/views/now?playbackId=playback_id&breakdownBy%5B%5D=browser' \ --header 'Authorization Bearer ' ``` For more information on the API and SDK methods, refer to the [API reference](/api-reference/viewership/get-realtime-viewership). #### [Creator metrics API](/api-reference/viewership/get-creators-metrics) This API can be called from the frontend to retrieve detailed engagement metrics for a specific resource. The only restriction it has is that it has to be called with an `assetId` or `streamId` values, which are private values today that only the asset creator should have access. Soon this creator ownership will be validated using wallet signatures instead. The idea for this API is to build creator analytics dashboards. It can be called from the frontend and can provide detailed insights to creators about their assets and streams. ```sh theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl --request GET \ --url 'https://livepeer.studio/api/data/views/query/creator?from=from&to=to&timeStep=day&assetId=asset_id_here&breakdownBy%5B%5D=browser' \ --header 'Authorization: Bearer ' ``` For more information on the API and SDK methods, refer to the [API reference](/api-reference/viewership/get-creators-metrics). #### [All metrics API](/api-reference/viewership/get-usage-metrics) This API can be called from the backend to retrieve detailed engagement metrics for a specific resource. It requires a non-CORS API key to be used, meaning that it cannot be called from the frontend. This is for security reasons since this API provides full access to all metrics in the account, meaning it provides the most flexibility in queries. The idea is that developers should build their own abstraction and access control logics on top of this API and abstract it as higher level API in their apps. ```sh theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl --request GET \ --url 'https://livepeer.studio/api/data/usage/query?timeStep=hour&breakdownBy%5B%5D=creatorId&from=from&to=to' \ --header 'Authorization: Bearer ' ``` For more information on the API and SDK methods, refer to the [API reference](/api-reference/viewership/get-usage-metrics). ### Visualizing Your Data If you are ready to take a deep dive into your data, then visualizations are the next step. We have put together a tutorial complete with templates to get you started. #### Visualize Engagement Metrics with Grafana If you are interested in visualizing your engagement metrics with Grafana, feel free to check out our tutorial, [Visualize Engagement Metrics with Grafana](/developers/guides/get-engagement-analytics-via-grafana), to learn more. # Engagement via Grafana Source: https://docs.livepeer.org/v1/developers/guides/get-engagement-analytics-via-grafana Learn how to visualize your engagement metrics with Grafana In May 2023, we released powerful engagement metrics offering detailed information on viewer behavior and playback quality on your platform. The API includes engagement metrics such as view counts and watch time, as well as performance metrics such as error rate, time to first frame, rebuffer ratio, and exit-before-starts across a variety of dimensions. For more details, check out [the docs here][1]. In this tutorial we will introduce a simple, free, and customizable method to quickly visualize the core metrics and dimensions of the API. ## Prerequisites Before you begin this tutorial, make sure you have: 1. Integrated the necessary components to capture viewership data. Details [here][2]. 2. Viewed videos via (1) to collect data. 3. Created an [CORS-enabled API Key][3]. The key must allow CORS access from your Grafana origin, or from all (`*`) 4. Set up a Grafana account and workspace with permissions to add new dashboard (and datasource/connection, if necessary). A free account is sufficient and available at [grafana.com][4] 5. Install the [`JSON API`][marcusolsson-json-datasource] plugin for grafana. With that we are ready to set up our dashboard! ## Setting up the Engagement Dashboard * Login to Grafana * Click "Connections" > "Connect Data" * Set up your JSON API: 1. Name: e.g. "Livepeer Engagement Data" 2. URL: [`https://livepeer.studio/api/data/views/query`][5] 3. Authentication methods: Forward OAuth Identity 4. TLS Settings: Skip TLS certificate validation 5. Custom HTTP Headers 1. Header: "Authorization" 2. Value: Full Access `Bearer ` * Save and Test * ["Import" the dashboard][6] from the official [Livepeer Studio Viewership Engagement dashboard][dashboard] and using the JSON API datasource created above. * Rename the dashboard if you'd prefer [1]: /developers/guides/get-engagement-analytics-via-api [2]: /developers/guides/get-engagement-analytics-via-api#registering-views [3]: /api-reference/overview/authentication [4]: https://grafana.com/ "Grafana" [5]: /api-reference/viewership/get-viewership-metrics [6]: https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/#import-a-dashboard [dashboard]: https://grafana.com/grafana/dashboards/20511-livepeer-studio-user-engagement/ [marcusolsson-json-datasource]: https://grafana.com/grafana/plugins/marcusolsson-json-datasource/ "JSON API Grafana Plugin" # Engagement via Timeplus Source: https://docs.livepeer.org/v1/developers/guides/get-engagement-analytics-via-timeplus Learn how to analyze Livepeer video engagement metrics with Timeplus Video engagement metrics are important to video creators, serving as valuable indicators of content quality, helping users manage their time effectively, facilitating interaction with content creators and other viewers, and contributing to the overall user experience on video-sharing platforms. In May 2023, Livepeer released their version of these [engagement metrics](/developers/guides/get-engagement-analytics-via-api) offering detailed information on viewer behavior and playback quality on your platform. The API includes engagement metrics such as view counts and watch time, as well as performance metrics such as error rate, time to first frame, rebuffer ratio, and exit-before-starts across a variety of dimensions. There are many different data analysis tools available today which can be used to help analyze these engagement metrics. Typically, users need to import the data into a data platform, and then build queries (SQL or non-SQL based) and dashboards on that platform to support interactive data exploration or monitoring what’s happening with those metrics data using visualizations. # What is Timeplus? Timeplus is a real-time streaming data analytics platform, it provides analytics functionality combined with both real-time streaming data and historical batching data. You can take it as a combination of streaming processing (such as Apache Flink) + real-time OLAP (such as Apache Druid). Timeplus is a great tool that can be used for engagement metrics analysis due to: * Timeplus offers query analytics capabilities based on SQL, making it user-friendly for those already proficient in SQL. * Timeplus delivers ultra-low latency real-time queries, instantly delivering analytical results to users as events occur. * Timeplus facilitates the extraction of query time information, eliminating the need for traditional ETL processes. Users can swiftly create analytics by combining diverse data sources. # How to get started using Timeplus with Livepeer's Engagement Data Analyzing Livepeer engagement metrics on Timeplus is incredibly easy. Based on the [Timeplus terraform provider](https://www.timeplus.com/post/timeplus-terraform-provider), users can create the whole analytic solution with a few simple commands. Here is the process, assuming you have both a Livepeer studio account and a Timeplus workspace created. 1. Create a [Livepeer API Key](/api-reference/overview/authentication) 2. Create your [Timeplus API Key](https://docs.timeplus.com/apikey) 3. Install terraform [https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) 4. Download the Livepeer terraform resource definition from [https://github.com/timeplus-io/livepeer-source/blob/main/stacks/main.tf](https://github.com/timeplus-io/livepeer-source/blob/main/stacks/main.tf) to your local directory 5. Open a terminal from the directory and add following environments ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} export TF_VAR_timeplus_apikey=your_timeplus_api_key export TF_VAR_timeplus_workspace=your_timeplus_workspace_id export TF_VAR_timeplus_endpoint=timeplus_cloud_endpoint export TF_VAR_livepeer_apikey=your_livepeer_apikey ``` 6. Deploy the resources to Timeplus Cloud by run the following commands ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} terraform init terraform apply ``` This resource template will help users to create the following resources in Timeplus: * A [stream](https://docs.timeplus.com/working-with-streams) of engagement metrics with name `livepeer_viewership_metrics_kv` * A Livepeer [source](https://docs.timeplus.com/source) which will periodically pull data from Livepeer API and store the metrics data into the defined stream * A [user defined function](https://docs.timeplus.com/udf) (UDF) that turns the geohash into geo locations with longitude and latitude. * A [dashboard](https://docs.timeplus.com/viz#dashboard) that contains the following: * Hourly Views and Watch Time * Engagement by OS * View count by Video (Top 5) * View count by Device Type (Top 5) * Rebuffering Percentage * Time to First Frame * View By Geo Locations Here is the dashboard that you will instantly get after the resources being deployed to Timeplus: # Listen to asset events Source: https://docs.livepeer.org/v1/developers/guides/listen-to-asset-events Learn how to listen to asset events using Studio webhooks. Livepeer Studio uses webhooks to communicate with your application asynchronously when events for your asset occur. For example, you may want to know when an asset has been `uploaded` or is `ready`, so that you can surface this information to viewers. When these events happen, you can configure Studio to make a `POST` request to a webhook URL that you specify. ### Type of asset events | | | | --------------- | ------------------------------------------------------------------------------------------------------------------------ | | `asset.created` | This fires when an On Demand asset is created. | | `asset.updated` | This fires when an On Demand asset is updated. The asset payload will contain a playback URL when playback is available. | | `asset.ready` | This fires when an On Demand asset is ready. Playback will be available with all transcoded renditions. | | `asset.failed` | This fires when an On Demand asset fails during the upload or during processing. | | `asset.deleted` | This fires when an On Demand asset is deleted. | ### Set up a webhook endpoint The first step is to set up a webhook endpoint in your application. This is the URL that Livepeer Studio will send the event to - learn more about [setting up a webhook endpoint](/developers/guides/setup-and-listen-to-webhooks). ### Add a webhook URL to Livepeer Studio Log in to the [Livepeer Studio](https://livepeer.studio/) and navigate to the [Developers/Webhooks](https://livepeer.studio/dashboard/developers/webhooks) page. Webhooks Click the "Create Webhook" button and enter the URL of the webhook endpoint. Select any asset event (with an `asset` prefix) and click "Create Webhook". # Listen to stream events Source: https://docs.livepeer.org/v1/developers/guides/listen-to-stream-events Learn how to listen to stream events using Studio webhooks Livepeer Studio uses webhooks to communicate with your application asynchronously when events for your stream occur. For example, you may want to know when a stream is `active` or `idle`, so that you can surface this information to viewers. When these events happen, you can configure Studio to make a `POST` request to a webhook URL that you specify. ### Type of stream events | | | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stream.started` | The parent stream object's isActive value is marked as true and the .m3u8 HLS URL works | | `stream.idle` | The parent stream object's isActive value should be marked as false and the .m3u8 HLS URL no longer works | | `recording.ready` | This fires when a recording is ready to be downloaded | | `recording.started` | This fires when recording has started on an active stream | | `recording.waiting` | This fires after a stream with recording on has concluded and is not yet ready to be downloaded. Typically it takes 5 minutes for recordings to be ready for download. | | `multistream.connected` | This fires when we've successfully connected to the multistream target | | `multistream.error` | This fires when we've encountered an error either while attempting to connect to the third party streaming service or while broadcasting. | | `multistream.disconnected` | This fires when we are no longer sending video to the multistream target. | ### Set up a webhook endpoint The first step is to set up a webhook endpoint in your application. This is the URL that Livepeer Studio will send the event to. Learn more about [setting up a webhook endpoint](/developers/guides/setup-and-listen-to-webhooks). ### Add a webhook URL to Livepeer Studio Log in to the [Livepeer Studio](https://livepeer.studio/) and navigate to the [Developers/Webhooks](https://livepeer.studio/dashboard/developers/webhooks) page. Webhooks Click the "Create Webhook" button and enter URL of webhook endpoint. Select any stream event (with stream/multistream prefix) to receive notifications for and click "Create Webhook". # In-browser broadcasting Source: https://docs.livepeer.org/v1/developers/guides/livestream-from-browser Learn how to broadcast using WebRTC We demonstrate below how to [broadcast from the web](/developers/core-concepts/studio/in-browser-broadcast) using Livepeer's low latency WebRTC broadcast. Developers can either use the Livepeer Broadcast React component, or build their own WebRTC solution. ## Using UI Kit Broadcast The example below shows how to use the Livepeer UI Kit [`Broadcast`](/sdks/react/broadcast/Root) component to broadcast from the web. ### Broadcast This guide assumes you have configured a Livepeer JS SDK client with an API key. We can use the [`Broadcast`](/sdks/react/broadcast/Root) primitives with a stream key, from a stream we created. We show some simple styling below with Tailwind CSS, but this can use any styling library, since the primitives ship as unstyled, composable components. ```tsx Broadcast.tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { EnableVideoIcon, StopIcon } from "@livepeer/react/assets"; import * as Broadcast from "@livepeer/react/broadcast"; import { getIngest } from "@livepeer/react/external"; const streamKey = "your-stream-key"; export const DemoBroadcast = () => { return (
LIVE
LOADING
IDLE
); }; ``` ## Embeddable broadcast This is one of the easiest ways to broadcast video from your website/applications. You can embed the iframe on your website/applications by using the below code snippet. You can replace the `STREAM_KEY` with your stream key for the stream. ```html theme={"theme":{"light":"github-light","dark":"dark-plus"}} ``` This will automatically stream from the browser with a fully composed UI, using STUN/TURN servers to avoid network firewall issues. ## Adding custom broadcasting If you want to add custom broadcasting to your app and handle the WebRTC SDP negotiation without using the Livepeer React primitives, you can follow the steps below. ### Get the SDP Host First, you will need to make a request to get the proper ingest URL for the region which your end user is in. We have a global presence, and we handle redirects based on GeoDNS to allow users to get the lowest latency server. To do this make a `HEAD` request to the WebRTC redirect endpoint: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} curl -I 'https://livepeer.studio/webrtc/{STREAM_KEY}' ... > Location: https://lax-prod-catalyst-2.lp-playback.studio/webrtc/{STREAM_KEY} ``` We are only interested in getting the redirect URL from the response, so that we can set up the correct ICE servers. From the above response headers, the best WebRTC ingest URL for the user is `https://lax-prod-catalyst-2.lp-playback.studio/webrtc/{STREAM_KEY}`. We will use this in the next step. The process will change in the future to remove the need for this extraneous `HEAD` request - please check back later. ### Broadcast Now that we have the endpoint for the ICE servers, we can start SDP negotiation following the [WHIP spec](https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html) and kick off a livestream. The outline of the steps are: 1. Create a new `RTCPeerConnection` with the ICE servers from the redirect URL. 2. Construct an SDP offer using the library of your choice. 3. Wait for ICE gathering. 4. Send the SDP offer to the server and get the response. 5. Use the response to set the remote description on the `RTCPeerConnection`. 6. Get a local media stream and add the track to the peer connection, and set the video element `src` to the `srcObject`. ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}} // the redirect URL from the above GET request const redirectUrl = `https://lax-prod-catalyst-2.lp-playback.studio/webrtc/{STREAM_KEY}`; // we use the host from the redirect URL in the ICE server configuration const host = new URL(redirectUrl).host; const iceServers = [ { urls: `stun:${host}`, }, { urls: `turn:${host}`, username: "livepeer", credential: "livepeer", }, ]; // get user media from the browser (which are camera/audio sources) const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true, }); const peerConnection = new RTCPeerConnection({ iceServers }); // set the media stream on the video element element.srcObject = mediaStream; const newVideoTrack = mediaStream?.getVideoTracks?.()?.[0] ?? null; const newAudioTrack = mediaStream?.getAudioTracks?.()?.[0] ?? null; if (newVideoTrack) { videoTransceiver = peerConnection?.addTransceiver(newVideoTrack, { direction: "sendonly", }) ?? null; } if (newAudioTrack) { audioTransceiver = peerConnection?.addTransceiver(newAudioTrack, { direction: "sendonly", }) ?? null; } /** * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer * We create an SDP offer here which will be shared with the server */ const offer = await peerConnection.createOffer(); /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription */ await peerConnection.setLocalDescription(offer); /** Wait for ICE gathering to complete */ const ofr = await new Promise((resolve) => { /** Wait at most five seconds for ICE gathering. */ setTimeout(() => { resolve(peerConnection.localDescription); }, 5000); peerConnection.onicegatheringstatechange = (_ev) => { if (peerConnection.iceGatheringState === "complete") { resolve(peerConnection.localDescription); } }; }); if (!ofr) { throw Error("failed to gather ICE candidates for offer"); } /** * This response contains the server's SDP offer. * This specifies how the client should communicate, * and what kind of media client and server have negotiated to exchange. */ const sdpResponse = await fetch(redirectUrl, { method: "POST", mode: "cors", headers: { "content-type": "application/sdp", }, body: ofr.sdp, }); if (sdpResponse.ok) { const answerSDP = await sdpResponse.text(); await peerConnection.setRemoteDescription( new RTCSessionDescription({ type: "answer", sdp: answerSDP }) ); } ``` We just negotiated following the WHIP spec (which outlines the structure for the POST requests seen above) and we did SDP negotiation to create a new livestream. We then retrieved a local camera source and started a broadcast! To make the above process clearer, here is the flow (credit to the authors of the WHIP spec): The final HTTP DELETE is not needed for our media server, since we detect the end of broadcast by the lack of incoming packets from the gateway. ```txt WHIP Outline theme={"theme":{"light":"github-light","dark":"dark-plus"}} +-----------------+ +---------------+ +--------------+ +----------------+ | WebRTC Producer | | WHIP endpoint | | Media Server | | WHIP Resource | +---------+-------+ +-------+- -----+ +------+-------+ +--------|-------+ | | | | | | | | |HTTP POST (SDP Offer) | | | +------------------------>+ | | |201 Created (SDP answer) | | | +<------------------------+ | | | ICE REQUEST | | +----------------------------------------->+ | | ICE RESPONSE | | <------------------------------------------+ | | DTLS SETUP | | <==========================================> | | RTP/RTCP FLOW | | +------------------------------------------> | | HTTP DELETE | +------------------------------------------------------------>+ | 200 OK | <-------------------------------------------------------------x ``` # Managing Projects Source: https://docs.livepeer.org/v1/developers/guides/managing-projects Explore best practices for managing multiple environments in Livepeer Studio Create a project ## Overview Projects in Livepeer Studio allow for the organization of streams, assets, API keys, and usage within dedicated environments. This feature is helpful for separating staging and production environments, managing multiple applications, and ensuring efficient workflow within a single account. ## Why Use Projects? * **Separation of Environments**: Keep your staging and production environments separate to avoid conflicts and ensure reliable testing. * **Centralized Management for Multiple Applications**: Instead of juggling multiple accounts, you can build and manage separate applications from within the same account. This is ideal for developers and companies that operate multiple apps or brands and wish to streamline their management in one place. ## Getting Started with Projects ### Creating a New Project To start building with separate environments or applications, you'll need to create a new project. Here's how: 1. In the sidebar, click on the project dropdown at the top of the menu. 2. Choose **+ New project** from the dropdown list. 3. Enter a name for your new project when prompted. 4. Confirm the creation to set up your new environment. ### Renaming a Project 1. Within a project, navigate to the **Settings** section in the sidebar. 2. Find the project name field, make your changes, and save. ### Deleting a Project In the current version of Livepeer Studio, you **cannot** delete a project. But we are working on adding this feature soon. ## Conclusion With the introduction of Projects, Livepeer Studio provides you with a powerful way to manage your application's live and on-demand streams. By leveraging the ability to create separate projects for staging and production, you can streamline your workflows and ensure a clean separation of your streaming environments. # Monitor stream health Source: https://docs.livepeer.org/v1/developers/guides/monitor-stream-health Learn how to monitor stream metrics on Livepeer This guide provides instructions and information about using stream metrics to: * Monitor any active stream for issues that may impact the quality of your stream * Understand the metrics for operating a livestreaming or user generated content platform * View health status of your livestream (API and Dashboard No-Code Option) ## Metrics with Source Segments Duration The value of `sourceSegmentsDuration` returned is the duration in seconds of the asset source processed by Livepeer Studio. On the parent stream object, this value equates to the total amount of source video ingested by Livepeer Studio all time. On the session object, this value is the length of the livestream session (or the session recording). ### Request Use the [get stream](/api-reference/stream/get) endpoint to retrieve a stream, or ```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; const apiKey = 'YOUR_API_KEY'; const streamId = 'STREAM_ID'; const livepeer = new Livepeer({apiKey}); livepeer.stream.get(streamId) .then((response) => { console.log("Stream details:", response); }) .catch((error) => { console.error("Error fetching stream details:", error); }); ``` ```python theme={"theme":{"light":"github-light","dark":"dark-plus"}} import livepeer // Initialize the Livepeer client with your API key client = livepeer.Livepeer( api_key="", ) // Replan with the stream ID you want to retrieve res = client.stream.get(id='') if res.stream is not None: # handle response pass ``` ```go theme={"theme":{"light":"github-light","dark":"dark-plus"}} package main import( livepeergo "github.com/livepeer/livepeer-go" "context" "log" ) func main() { // Initialize the Livepeer client with your API key client := livepeergo.New( livepeergo.WithSecurity(""), ) // Define the stream ID you want to retrieve var id string = "" ctx := context.Background() res, err := client.Stream.Get(ctx, id) if err != nil { log.Fatal(err) } if res.Stream != nil { // handle response } } ``` **`GET /session/{id}` to retrieve a session object** ```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}} import { Livepeer } from "livepeer"; const apiKey = 'YOUR_API_KEY'; const sessionId = 'SESSION_ID'; const livepeer = new Livepeer({apiKey}); livepeer .session.get(sessionId) .then((response) => { console.log("Session details:", response); }) .catch((error) => { console.error("Error fetching session details:", error); }); ``` ```python theme={"theme":{"light":"github-light","dark":"dark-plus"}} import livepeer client = livepeer.Livepeer( api_key="", ) res = client.session.get(id='') if res.session is not None: # handle response pass ``` ```go theme={"theme":{"light":"github-light","dark":"dark-plus"}} package main import( livepeergo "github.com/livepeer/livepeer-go" "context" "log" ) func main() { client := livepeergo.New( livepeergo.WithSecurity(""), ) var id string = "" ctx := context.Background() res, err := client.Session.Get(ctx, id) if err != nil { log.Fatal(err) } if res.Session != nil { // handle response } } ``` ### Response * Returns a `200` response with the following object: ```json theme={"theme":{"light":"github-light","dark":"dark-plus"}} { "name": "test_stream", "id": "ijkl61f3-95bd-4971-a7b1-4dcb5d39e78a", "createdAt": 1596081229373, "streamKey": "abcd-uimq-jtgy-x98v", "playbackId": "efghb2mxupongp5k", "isActive": "true", "lastSeen": 1596119750455, "sourceSegments": 1360, "transcodedSegments": 5480, "sourceSegmentsDuration": 2630.53200000000004, "transcodedSegmentsDuration": 10620.1280000000004 // other asset object keys, like profiles and parentId } ``` ### Global Health Status * `Healthy` indicates that all the Health Checks are in a `Healthy` state. * `Unhealthy` indicates that one or more of the Health Checks are in an `Unhealthy` state * `Idle` indicates that the stream is not currently active ### Health Checks * ***Transcoding*** — A `Healthy` status indicates that video transcoding is occurring, and the multiple profiles of your stream are available for playback. An `Unhealthy` status indicates that we have failed to generate the profiles, so only the original video will be available. Check the event log below for any actionable errors, or check the [Status page](https://livepeer.statuspage.io/) for platform-wide issues. * ***Real-time —*** This indicates whether our system transcoded the stream faster than in real-time. In other words, if the video transcodes, latency is lower than the video duration. Unconventional configurations may cause an `Unhealthy` status. * ***Multistreaming —*** This health check indicates whether all configured `multistream targets` are connected. If no targets exist, this will appear blank and not be considered for the global health state. If this shows up as `Unhealthy`, you can also check the status of the individual `multistream targets` in the `Overview` tab, where the targets can be either `Idle`, `Pending`, `Online'or' Offline`: * An `Idle` status indicates that the stream is not currently active, so neither is multistreaming. * A `Pending` status indicates that our system tries connecting to the respective `multistream target`. * An `Online` status indicates that the `multistream target` is successfully connected. * An `Offline` status means that we've received an error when trying to connect to the target. To try again, check the ingest URL and stream key of your destination service, re-configure your `multistream target` and restart your stream. ### Logs Logs will surface informational alerts (ex., stream has started/stopped, multistream destinations have connected/disconnected) or any fatal errors from the transcoding process. Here is an example of a fatal error: ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}} Transcode error from ewr-prod-livepeer-orchestrator-0 for segment 0: **Unsupported input pixel format** ``` If you run into this or any similar errors, check the configuration in your streaming software (e.g., OBS) and restart the stream. ### Session Ingest Rate Session ingest rate tells you the bitrate of the video received by Livepeer ingest servers, updated every 10 seconds. A high bitrate is suggestive that streams into the Livepeer system are high quality, your encoder is working correctly, and your internet connection is good. A fluctuating or low bitrate may suggest that your encoder is misconfigured or that the streamer internet connection isn't as strong as it should be to deliver high-quality streams to your viewers. ### Monitoring in the Dashboard In the dashboard, there will be a `health` tab on the right side: ![image](https://user-images.githubusercontent.com/79172645/135450248-bf3edea4-e666-4927-8114-f972cd2879fe.png) During any Active stream, components on this page will display a variety of health indicators. ![image](https://user-images.githubusercontent.com/79172645/135450296-168bd797-fafb-4371-af8a-86b346bbaa18.png)
![image](https://user-images.githubusercontent.com/79172645/135450340-4abed968-df77-473d-b926-88c11b5799d2.png) At the bottom of the page, a chart will show the ingest rate of a livestream: ![image](https://user-images.githubusercontent.com/79172645/135467197-08ec63ab-4c44-4cad-a0d5-95a76b80e5c5.png) ### Monitor with the API We are working on building a first-class solution to monitor stream health using the API. Please reach out to us on Discord if you have any input. # Set up a multistream Source: https://docs.livepeer.org/v1/developers/guides/multistream Learn how to add multistreaming [Multistreaming](/developers/core-concepts/core-api/multistream) allows a source stream and transcoded renditions to be pushed to multiple RTMP(S) targets, such as Twitch, Facebook Live, and YouTube Live. ## Multistream targets with the API You can manage multistream target objects from the API - see the [API reference](/api-reference/multistream/create). Each target represents a specific endpoint where a stream could be multistreamed to. Note that the creation of a multistream target itself is not enough - you also need to reference the target from the stream object that should be multistreamed. To avoid managing the separate multistream target objects, you can also create the targets inlined in the stream [creation](/api-reference/stream/create) and [update](/api-reference/stream/update) APIs. You can also create and remove multistream targets individually from a given stream using the [Create Multistream Target](/api-reference/stream/add-multistream-target) and [Delete Multistream Target](/api-reference/stream/delete-multistream-target) APIs. Any changes to multistream targets, including creating new targets, will apply only to the next active session. If you make changes while a stream is active, those changes will not apply until the current session has ended and a new session begins. ## Multistream targets in the Studio dashboard To add, manage, and delete multistream targets, navigate to the [Streams page](https://livepeer.studio/dashboard/streams) and click on a stream name. On the stream detail page, on the Overview tab, you can view all configured targets. You must configure new multistream targets for each stream. This will apply the multistream configuration to that stream. Multistream targets **do not carry over when you create new streams.** To add a new target, click `Create` on the right side of the multistream targets section. You'll be asked to name your target, provide the Ingest URL and a stream key, Ingest URL being the only required value. The Ingest URL and stream key should be provided by the platform that you're streaming into and can one of: RTMP, RTMPS, or SRT protocols. Select the rendition profile that you'd like to send to that target and click `Create Target`. Once created, you can toggle the targets on and off. To edit a multistream target, click on the three dots to the right of the target name and then on `Edit` to open up the target settings. Select `Update Target` when you've completed your changes. To delete a multistream target, click on the three dots to the right of the target name and then on `Delete`. Any changes to multistream targets, including creating new targets, will apply only to the next active session. If you make changes while a stream is active, those changes will not apply until the current session has ended and a new session begins. ## Understanding Multistream Performance ### Dashboard While a session is active that has multistream targets configured, you'll be able to see if a destination is `Active` or `Offline`. There may be a slight delay between starting the stream and the push destinations connecting. When starting to stream to multistream targets, use these indicators to understand whether it has connected to its target(s). On the stream page where you've configured your multistream targets, select the Health tab at the top. While a session is active, you'll see the ingest rate of the source stream. ### Webhooks There are three webhooks to help you monitor the state of your multistream targets: * `multistream.connected`: When Studio has successfully connected to the multistream target and you're also online in the other service you are multistreaming to. * `multistream.error`: In case any error has occurred during connection to the multistream target. This means some kind of problem with the configuration or the other service you are multistreaming to. * `multistream.disconnected`: Sent after a stream is ended to indicate that Multistream has also ended for the respective target. # Optimize latency Source: https://docs.livepeer.org/v1/developers/guides/optimize-latency-of-a-livestream Learn how to optimize latency for your Livepeer Studio workflow. One of the first questions that many developers ask during Livepeer Studio onboarding is: "How do we optimize for low latency?" Many live streaming apps and sites rely on low latency to promote engagement and interaction between viewers and streamers. Latency refers to the lapse in time between a camera capturing an action in real life and viewers of a livestream seeing it happen on their screen. Ultra-low latency is when that time is short enough, typically 0.5 - 3 seconds, so viewers of a live-streaming application can interact with what's happening in the stream in a way that feels natural. There’s a range of latencies to support a variety of use cases; low or ultra-low latency is a common goal, but it is one of many viable latency choices. The right latency depends on what you’re trying to achieve. In this primer, we provide an overview of Livepeer Studio’s approach to latency. By the end of the primer, you should have an understanding of how to achieve the right latency / quality balance for your workflow. ## Understanding Protocols Livepeer Studio delivers video using several protocols. The most common (though not the only) delivery protocols supported by Livepeer Studio are **HLS** and **WebRTC**. ### Delivering video with HLS HLS (HTTP Live Streaming), initially developed by Apple for iOS devices, serves video with a series of segmented `.ts` files. It is broadly supported across many device types and is extremely well-optimized for serving multiple renditions; these characteristics position HLS as the default choice for many gateways. However, HLS has very high overhead and latency. Specifically, its chunked segment delivery means that viewers must wait for the current segment to finish downloading before they can start viewing it. Similarly, HLS players often buffer a few segments in advance to ensure smooth playback and to handle network fluctuations. This buffer introduces additional latency, as the player waits to accumulate enough data before starting playback. Buffer behavior is heavily dependent on client implementation. **HLS delivers video with standard latency of 10-20 seconds. Streaming HLS with Livepeer Studio’s recommended low-latency settings lead to \~10s latency.** ### Delivering video with WebRTC WebRTC (Web Real-Time Communication) is a streaming protocol known for its low-latency capabilities, making it suitable for real-time communication applications like video conferencing, online gaming, and live streaming. Unlike HLS, which is designed for adaptive streaming and on-demand content, WebRTC focuses on minimizing latency. WebRTC offers low latency by prioritizing real-time communication, direct peer-to-peer connections, UDP transport, and adaptive streaming while minimizing intermediaries. It is a preferred choice for applications where minimizing delay is critical, such as video conferencing and live interactive streaming. **WebRTC delivers video with ultra-low latency of 0.5 - 3 seconds.** # Optimizing Playback for Low Latency ## Lowest possible latency (WebRTC playback) WebRTC is available as a delivery protocol for all streams regardless of input protocol; to achieve the lowest possible latency of 0.5 - 3 seconds, use of WebRTC delivery is required. We implement the WHIP/WHEP spec with [a few minor nuances around SDP negotiation](/v1/developers/guides/livestream-from-browser#adding-custom-broadcasting). To play back a WebRTC rendition, you will need to use a WebRTC-compatible player, such as the Livepeer Player. * [Playing WebRTC livestreams with the Livepeer Player](/v1/developers/guides/playback-a-livestream) * [Playing WebRTC livestreams with other players](/v1/developers/guides/playback-a-livestream#using-your-own-player) Please note that **if b-frames are present in a livestream, WebRTC renditions will not be available**. This is because in WebRTC video playback, b-frames will appear out-of-order on most systems. ## Optimizing for Lower Latency (HLS and WebRTC ingest and playback) Depending on your goals, you may or may not want to optimize for the lowest latency; for example, if your user base has a high percentage of low-bandwidth mobile users, it may be preferable to optimize for bandwidth efficiency over latency. ### Streaming with OBS Achieving the right balance between low latency and stream quality is essential for providing the best possible user experience. Two settings that significantly impact your stream quality, latency, and user experience are: * **Rate Control:** This setting dictates the bitrate or "quality" of the stream. A high amount of bandwidth usually means better quality, but keep in mind that your output can never improve the quality of your stream beyond your input. * **Keyframe Interval:** Video streams consist of full frames and data relative to the full frames. This setting determines how often a full frame appears, which heavily influences latency. To allow streamers to easily configure OBS for best performance with Livepeer Studio, we’ve created an OBS preset that can be selected in the OBS Settings panel. You can read more about configuring OBS here * [Selecting the Livepeer Studio preset](/v1/developers/guides/stream-via-obs#update-stream-settings) * [Optimizing OBS for low latency](/v1/developers/guides/stream-via-obs#optimizing-for-low-latency) * [Optimizing for Stream Quality](/v1/developers/guides/stream-via-obs#optimizing-for-stream-quality-and-user-experience) Finally, we’ve compiled a set of recommended settings to achieve specific experience goals (e.g., highest quality but high latency, average quality but lower latency, or a balanced approach): * [Sample settings](/v1/developers/guides/stream-via-obs#recommended-obs-settings) ## In-browser broadcasting Livepeer Studio allows users to broadcast from within their browser using WebRTC broadcast. **These broadcasts are optimized for low latency by default** and can play back in HLS (8-10s latency) or WebRTC (0.5 - 3s latency). You can leverage this capability in three ways: * [Implement the customizable React component](/v1/developers/guides/livestream-from-browser) * [Embed a hosted version](/v1/developers/guides/livestream-from-browser#embeddable-broadcast) * [Roll your own broadcasting experience with plain WebRTC](/v1/developers/guides/livestream-from-browser#adding-custom-broadcasting) # Smoke testing your workflow When you first implement your livestreaming workflow, you may see higher-than-expected latency. This is common and likely means that a few settings need to be tweaked. ### Check latency using the Livepeer Player Go to `https://lvpr.tv/?v=` and observe the latency. This will help isolate the cause of the latency. If you are seeing higher-than-expected latency on the Livepeer Player (>15s for HLS or >4s for WebRTC), it suggests something about the incoming stream is causing high latency. Check your keyframe interval, bitrate, and b-frame settings. Viewers changing the resolution will also impact the latency for WebRTC, since this will necessarily incur transcode latency. ### If you’re using another HLS player, compare your HLS config to the Livepeer Player Our defaults can be found [here](https://github.com/livepeer/react/blob/47e572880224739f538e75ef192bb290b85a852f/packages/core-web/src/media/browser/hls/index.ts#L55-L61). Please note that this config is a starting point that we feel is a good balance for latency, quality, rebuffering, etc. ### Reach out to the Livepeer Studio team We will be happy to help troubleshoot so that you are able to achieve 0.5-3s latency with WebRTC or \~10s latency with HLS for the bulk of your users. Often, we achieve improvements by helping you tweak broadcasting settings or optimizing HLS configs. # Overview Source: https://docs.livepeer.org/v1/developers/guides/overview Practical step-by-step guides to help you achieve a specific goal. These guides are designed to help you get started with a specific task or feature. ### Video On Demand Upload a video asset to Livepeer Play a video asset with Player Upload and store encrypted videos. Set up webhook to receive notifications. Transcode videos programmatically Retrieve analytics via Livepeer API. Visualize analytics using Grafana. Visualize analytics using Timeplus. ### Livestream Start a live video broadcast. Watch live broadcasts in real-time. Broadcast using OBS Go live directly from your web browser. Keep track of the livestream's health Set up webhook to receive notifications. Livestream to multiple platforms at once. Clip a livestream ### Access control Control access using webhooks Control access using JWTs ### Webhooks Set up webhook to receive notifications. # Play a livestream Source: https://docs.livepeer.org/v1/developers/guides/playback-a-livestream Learn how to use a media player with Livepeer In this guide, we demonstrate how to play livestreams in your application. We do not recommend using ["CORS-enabled" API keys](/api-reference/overview/authentication) - they will be deprecated in an upcoming release. We recommend making requests from your backend to the Livepeer Studio API. ## Using the UI Kit Player The example below show to use the UI Kit [`Player`](/sdks/react/player/Root) to play a livestream. ``` ### Low Latency In the embeddable player, livestreams will, by default, play back with low-latency WebRTC. If this does not succeed in playing back (rarely, usually due to a slow network or connectivity issues), the embeddable player will automatically fall back to HLS playback. Also, if the stream contains B-frames (or bidirectional frames, which are common for users streaming with OBS or other streaming apps), the Player will automatically fall back to HLS, so that out-of-order frames are not displayed. *This only applies to users who are playing livestreams.* If you do not want to use WebRTC, you can pass `&lowLatency=false` in the query string, or if you want *only* low latency, you can pass `&lowLatency=force`. OBS users should be instructed to use the Livepeer Studio stream profile, or to manually turn off B-frames in their stream. See our [Stream from OBS](/developers/guides/stream-via-obs) docs for more information. ### Clipping To enable clipping, `&clipLength={seconds}` can be passed, which will allow viewers to clip livestreams. The length in seconds **must be less than 120 seconds**. ### Constant Playback The embed supports "constant" playback with `constant=true`, which means that audio will not be distorted if the playhead falls behind the livestream. This is usually used for music applications, where audio quality/consistency is more important than latency. ### Other Configs You can also override the default `muted` and `autoplay` behavior with `&muted=false` and/or `&autoplay=false`. These are set to true by default. Looping can also be set with `&loop=true`. ``` ### Configuration If you are using the iframe for livestreams as well as assets, see the livestream embed docs for how to set up low latency, clipping, and other configs for streams. You can override the default `muted` and `autoplay` behavior with `&muted=false` and/or `&autoplay=false`. These are set to true by default. Looping can also be set with `&loop=true`.