Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@api.video/media-recorder

Package Overview
Dependencies
Maintainers
0
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@api.video/media-recorder - npm Package Compare versions

Comparing version 1.0.10 to 1.0.11

23

CHANGELOG.md
# Changelog
All changes to this project will be documented in this file.
## [1.0.11] - 2024-09-25
- Fix parallel uploadLastPart
## [1.0.10] - 2023-02-07
- Make `getSupportedMimeTypes` static
## [1.0.9] - 2023-02-07
- Add `mimeType` and `generateFileOnStop` options
## [1.0.8] - 2023-01-23
- Add "videoPlayable" event
## [1.0.7] - 2022-10-10
- Allow the user to customize the recorded video's name
## [1.0.6] - 2022-07-06
- Update dependancies

@@ -22,2 +32,3 @@ - Add origin headers

## [1.0.5] - 2022-05-31
- Add `getMediaRecorderState()` method

@@ -27,18 +38,24 @@ - Fix `stop()` method when the recorder is not started

## [1.0.4] - 2022-05-24
- Prevent last uploaded part to be empty
## [1.0.3] - 2022-03-23
- Specify a return type for `ApiVideoMediaRecorder.stop`
## [1.0.2] - 2022-03-07
- Make the start() timeslice parameter customizable
## [1.0.1] - 2021-11-25
- Bump dependancies
## [1.0.0] - 2021-11-16
- Bump dependancies
- Readme update
## [0.0.1] - 2021-11-01
- First version

10

CONTRIBUTING.md

@@ -49,3 +49,3 @@ # Contributing to api.video

Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/bug_report.md), the information it asks for helps us resolve issues faster.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/bug_report.yml), the information it asks for helps us resolve issues faster.

@@ -62,3 +62,3 @@ > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.

Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined which repository your bug is related to, create an issue on that repository and provide the following information by filling in [the template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/bug_report.md).
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined which repository your bug is related to, create an issue on that repository and provide the following information by filling in [the template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/bug_report.yml).

@@ -90,3 +90,3 @@ Explain the problem and include additional details to help maintainers reproduce the problem:

When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](https://github.com/apivideo/.github/blob/main/.github/ISSUE_TEMPLATE/feature_request.yml), including the steps that you imagine you would take if the feature you're requesting existed.

@@ -119,3 +119,3 @@

1. Follow all instructions in [the template](https://github.com/apivideo/.github/blob/main/PULL_REQUEST_TEMPLATE.md)
1. Explain what, why and how you resolved the issue. If you have a related issue, please mention it.
2. Follow the [style guides](#style-guides)

@@ -232,2 +232,2 @@ 3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing <details><summary>What if the status checks are failing?</summary>If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.</details>

[help-wanted]:https://github.com/search?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aapivideo+sort%3Acomments-desc
[help-wanted]:https://github.com/search?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aapivideo+sort%3Acomments-desc
import { ProgressiveUploaderOptionsWithAccessToken, ProgressiveUploaderOptionsWithUploadToken, VideoUploadResponse } from "@api.video/video-uploader";
import { VideoUploadError } from "@api.video/video-uploader/dist/src/abstract-uploader";
export { ProgressiveUploaderOptionsWithAccessToken, ProgressiveUploaderOptionsWithUploadToken, VideoUploadResponse } from "@api.video/video-uploader";
export { ProgressiveUploaderOptionsWithAccessToken, ProgressiveUploaderOptionsWithUploadToken, VideoUploadResponse, } from "@api.video/video-uploader";
export { VideoUploadError } from "@api.video/video-uploader/dist/src/abstract-uploader";

@@ -20,2 +20,3 @@ export interface Options {

private mimeType;
private previousPart;
constructor(mediaStream: MediaStream, options: Options & (ProgressiveUploaderOptionsWithUploadToken | ProgressiveUploaderOptionsWithAccessToken));

@@ -22,0 +23,0 @@ addEventListener(type: EventType, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined): void;

{
"name": "@api.video/media-recorder",
"version": "1.0.10",
"version": "1.0.11",
"description": "api.video media recorder - upload video from your webcam with ease",

@@ -42,5 +42,5 @@ "repository": {

"dependencies": {
"@api.video/video-uploader": "^1.1.3",
"@api.video/video-uploader": "^1.1.6",
"core-js": "^3.23.3"
}
}

@@ -0,1 +1,2 @@

<!--<documentation_excluded>-->
[![badge](https://img.shields.io/twitter/follow/api_video?style=social)](https://twitter.com/intent/follow?screen_name=api_video) &nbsp; [![badge](https://img.shields.io/github/stars/apivideo/api.video-typescript-media-recorder?style=social)](https://github.com/apivideo/api.video-typescript-media-recorder) &nbsp; [![badge](https://img.shields.io/discourse/topics?server=https%3A%2F%2Fcommunity.api.video)](https://community.api.video)

@@ -11,3 +12,3 @@ ![](https://github.com/apivideo/.github/blob/main/assets/apivideo_banner.png)

# Table of contents
## Table of contents
- [Table of contents](#table-of-contents)

@@ -21,3 +22,3 @@ - [Project description](#project-description)

- [Documentation](#documentation)
- [Instanciation](#instanciation)
- [Instantiation](#instantiation)
- [Options](#options)

@@ -36,13 +37,26 @@ - [Using a delegated upload token (recommended):](#using-a-delegated-upload-token-recommended)

# Project description
<!--</documentation_excluded>-->
<!--<documentation_only>
---
title: api.video TypeScript Media Recorder
meta:
description: The official api.video TypeScript Media Recorder for api.video. [api.video](https://api.video/) is the video infrastructure for product builders. Lightning fast video APIs for integrating, scaling, and managing on-demand & low latency live streaming features in your app.
---
# api.video TypeScript Media Recorder
[api.video](https://api.video/) is the video infrastructure for product builders. Lightning fast video APIs for integrating, scaling, and managing on-demand & low latency live streaming features in your app.
</documentation_only>-->
## Project description
Typescript library to easily upload data from a [MediaStream](https://developer.mozilla.org/fr/docs/Web/API/MediaStream) to api.video.
It can be used to upload a video to api.video from the user's webcam with ease, as well as from a screen recording.
# Getting started
## Getting started
## Installation
### Installation
### Installation method #1: requirejs
#### Installation method #1: requirejs

@@ -66,3 +80,3 @@ If you use requirejs you can add the library as a dependency to your project with

### Installation method #2: typescript
#### Installation method #2: typescript

@@ -87,3 +101,3 @@ If you use Typescript you can add the library as a dependency to your project with

### Simple include in a javascript project
#### Simple include in a javascript project

@@ -112,13 +126,13 @@ Include the library in your HTML file like so:

# Documentation
## Documentation
## Instanciation
### Instantiation
### Options
#### Options
The media recorder object is instanciated using a [MediaStream](https://developer.mozilla.org/fr/docs/Web/API/MediaStream) and an `options` object. Options to provide depend on the way you want to authenticate to the API: either using a delegated upload token (recommanded), or using a usual access token.
The media recorder object is instantiated using a [MediaStream](https://developer.mozilla.org/fr/docs/Web/API/MediaStream) and an `options` object. Options to provide depend on the way you want to authenticate to the API: either using a delegated upload token (recommanded), or using a usual access token.
#### Using a delegated upload token (recommended):
##### Using a delegated upload token (recommended):
Using delegated upload tokens for authentication is best options when uploading from the client side. To know more about delegated upload token, read the dedicated article on api.video's blog: [Delegated Uploads](https://api.video/blog/tutorials/delegated-uploads).
Using delegated upload tokens for authentication is best options when uploading from the client side. To know more about delegated upload token, read the dedicated article on api.video's blog: [Delegated Uploads](https://api.video/blog/tutorials/delegated-uploads/).

@@ -132,3 +146,3 @@

#### Using an access token (discouraged):
##### Using an access token (discouraged):

@@ -145,3 +159,3 @@ **Warning**: be aware that exposing your access token client-side can lead to huge security issues. Use this method only if you know what you're doing :).

#### Common options
##### Common options

@@ -156,3 +170,3 @@

### Example
#### Example

@@ -166,9 +180,9 @@ ```javascript

## Methods
### Methods
### `start(options?: { timeslice?: number })`
#### `start(options?: { timeslice?: number })`
The start() method starts the upload of the content retrieved from the MediaStream. It takes an optionnal `options` parameter.
#### Options
##### Options
| Option name | Mandatory | Type | Description |

@@ -182,3 +196,3 @@ | ----------: | ------------------ | ------ | ---------------------------------------------------- |

```javascript
// ... mediaRecorder instanciation
// ... mediaRecorder instantiation

@@ -190,7 +204,7 @@ mediaRecorder.start();

### `stop(): Promise<VideoUploadResponse>`
#### `stop(): Promise<VideoUploadResponse>`
The start() method stops the media recording. It upload the last part of content retrieved from the MediaStream (this will start the aggregation of the video parts on the api.video side). It takes no parameter. It returns a Promise that resolves with the newly created video.
### `addEventListener(event: string, listener: Function)`
#### `addEventListener(event: string, listener: Function)`

@@ -205,3 +219,3 @@ Define an event listener for the media recorder. The following events are available:

```javascript
// ... mediaRecorder instanciation
// ... mediaRecorder instantiation

@@ -213,3 +227,3 @@ mediaRecorder.addEventListener("error", (event) => {

### `getMediaRecorderState(): RecordingState`
#### `getMediaRecorderState(): RecordingState`

@@ -221,3 +235,3 @@ Return the state of the underlaying [MediaRecorder](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder). The state can be one of the following: `inactive`, `paused`, `recording`.

```javascript
// ... mediaRecorder instanciation
// ... mediaRecorder instantiation

@@ -228,3 +242,3 @@ mediaRecorder.stop()

# Full example
## Full example

@@ -231,0 +245,0 @@

@@ -1,11 +0,20 @@

import { ProgressiveUploader, ProgressiveUploaderOptionsWithAccessToken, ProgressiveUploaderOptionsWithUploadToken, VideoUploadResponse } from "@api.video/video-uploader";
import {
ProgressiveUploader,
ProgressiveUploaderOptionsWithAccessToken,
ProgressiveUploaderOptionsWithUploadToken,
VideoUploadResponse,
} from "@api.video/video-uploader";
import { VideoUploadError } from "@api.video/video-uploader/dist/src/abstract-uploader";
export { ProgressiveUploaderOptionsWithAccessToken, ProgressiveUploaderOptionsWithUploadToken, VideoUploadResponse } from "@api.video/video-uploader";
export {
ProgressiveUploaderOptionsWithAccessToken,
ProgressiveUploaderOptionsWithUploadToken,
VideoUploadResponse,
} from "@api.video/video-uploader";
export { VideoUploadError } from "@api.video/video-uploader/dist/src/abstract-uploader";
export interface Options {
onError?: (error: VideoUploadError) => void;
generateFileOnStop?: boolean;
mimeType?: string;
onError?: (error: VideoUploadError) => void;
generateFileOnStop?: boolean;
mimeType?: string;
}

@@ -15,6 +24,6 @@

try {
// @ts-ignore
PACKAGE_VERSION = __PACKAGE_VERSION__ || "";
// @ts-ignore
PACKAGE_VERSION = __PACKAGE_VERSION__ || "";
} catch (e) {
// ignore
// ignore
}

@@ -24,157 +33,182 @@

export class ApiVideoMediaRecorder {
private mediaRecorder: MediaRecorder;
private streamUpload: ProgressiveUploader;
private onVideoAvailable?: (video: VideoUploadResponse) => void;
private onStopError?: (error: VideoUploadError) => void;
private eventTarget: EventTarget;
private debugChunks: Blob[] = [];
private generateFileOnStop: boolean;
private mimeType: string;
private mediaRecorder: MediaRecorder;
private streamUpload: ProgressiveUploader;
private onVideoAvailable?: (video: VideoUploadResponse) => void;
private onStopError?: (error: VideoUploadError) => void;
private eventTarget: EventTarget;
private debugChunks: Blob[] = [];
private generateFileOnStop: boolean;
private mimeType: string;
private previousPart: Blob | null = null;
constructor(mediaStream: MediaStream, options: Options & (ProgressiveUploaderOptionsWithUploadToken | ProgressiveUploaderOptionsWithAccessToken)) {
this.eventTarget = new EventTarget();
this.generateFileOnStop = options.generateFileOnStop || false;
constructor(
mediaStream: MediaStream,
options: Options &
(
| ProgressiveUploaderOptionsWithUploadToken
| ProgressiveUploaderOptionsWithAccessToken
)
) {
this.eventTarget = new EventTarget();
this.generateFileOnStop = options.generateFileOnStop || false;
const findBestMimeType = () => {
const supportedTypes = ApiVideoMediaRecorder.getSupportedMimeTypes();
if (supportedTypes.length === 0) {
throw new Error("No compatible supported video mime type");
}
return supportedTypes[0];
}
const findBestMimeType = () => {
const supportedTypes = ApiVideoMediaRecorder.getSupportedMimeTypes();
if (supportedTypes.length === 0) {
throw new Error("No compatible supported video mime type");
}
return supportedTypes[0];
};
this.mimeType = options.mimeType || findBestMimeType();
this.mimeType = options.mimeType || findBestMimeType();
this.mediaRecorder = new MediaRecorder(mediaStream, {
mimeType: this.mimeType,
});
this.mediaRecorder = new MediaRecorder(mediaStream, {
mimeType: this.mimeType,
});
this.mediaRecorder.addEventListener("stop", () => {
const stopEventPayload = this.generateFileOnStop ? { file: new Blob(this.debugChunks, { type: this.mimeType }) } : {};
this.dispatch("recordingStopped", stopEventPayload);
});
this.mediaRecorder.addEventListener("stop", () => {
const stopEventPayload = this.generateFileOnStop
? { file: new Blob(this.debugChunks, { type: this.mimeType }) }
: {};
this.dispatch("recordingStopped", stopEventPayload);
});
this.streamUpload = new ProgressiveUploader({
preventEmptyParts: true,
...options,
origin: {
sdk: {
name: "media-recorder",
version: PACKAGE_VERSION
},
...options.origin
},
});
this.streamUpload = new ProgressiveUploader({
preventEmptyParts: true,
...options,
origin: {
sdk: {
name: "media-recorder",
version: PACKAGE_VERSION,
},
...options.origin,
},
});
this.mediaRecorder.ondataavailable = (e) => this.onDataAvailable(e);
(window as any).mediaRecorder = this.mediaRecorder;
}
this.mediaRecorder.ondataavailable = (e) => this.onDataAvailable(e);
public addEventListener(type: EventType, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined): void {
if (type === "videoPlayable") {
this.streamUpload.onPlayable((video) => this.dispatch("videoPlayable", video));
this.mediaRecorder.onstop = async () => {
if (this.previousPart) {
const video = await this.streamUpload.uploadLastPart(this.previousPart);
if (this.onVideoAvailable) {
this.onVideoAvailable(video);
}
this.eventTarget.addEventListener(type, callback, options);
}
} else if (this.onStopError) {
const error: VideoUploadError = {
raw: "No data available to upload",
title: "No data available to upload",
};
this.onStopError(error);
}
};
(window as any).mediaRecorder = this.mediaRecorder;
}
private async onDataAvailable(ev: BlobEvent) {
const isLast = (ev as any).currentTarget.state === "inactive";
try {
if (this.generateFileOnStop) {
this.debugChunks.push(ev.data);
}
if (isLast) {
const video = await this.streamUpload.uploadLastPart(ev.data);
if (this.onVideoAvailable) {
this.onVideoAvailable(video);
}
} else {
await this.streamUpload.uploadPart(ev.data);
}
} catch (error) {
if (!isLast) this.mediaRecorder.stop();
this.dispatch("error", error);
if (this.onStopError) this.onStopError(error as VideoUploadError);
}
public addEventListener(
type: EventType,
callback: EventListenerOrEventListenerObject | null,
options?: boolean | AddEventListenerOptions | undefined
): void {
if (type === "videoPlayable") {
this.streamUpload.onPlayable((video) =>
this.dispatch("videoPlayable", video)
);
}
this.eventTarget.addEventListener(type, callback, options);
}
private dispatch(type: EventType, data: any): boolean {
return this.eventTarget.dispatchEvent(Object.assign(new Event(type), { data }));
private async onDataAvailable(ev: BlobEvent) {
const isLast = (ev as any).currentTarget.state === "inactive";
try {
if (this.generateFileOnStop) {
this.debugChunks.push(ev.data);
}
if (this.previousPart) {
const toUpload = new Blob([this.previousPart]);
this.previousPart = ev.data;
await this.streamUpload.uploadPart(toUpload);
} else {
this.previousPart = ev.data;
}
} catch (error) {
if (!isLast) this.mediaRecorder.stop();
this.dispatch("error", error);
if (this.onStopError) this.onStopError(error as VideoUploadError);
}
}
public start(options?: { timeslice?: number }) {
if (this.getMediaRecorderState() === "recording") {
throw new Error("MediaRecorder is already recording");
}
this.mediaRecorder.start(options?.timeslice || 5000);
}
private dispatch(type: EventType, data: any): boolean {
return this.eventTarget.dispatchEvent(
Object.assign(new Event(type), { data })
);
}
public getMediaRecorderState(): RecordingState {
return this.mediaRecorder.state;
public start(options?: { timeslice?: number }) {
if (this.getMediaRecorderState() === "recording") {
throw new Error("MediaRecorder is already recording");
}
this.mediaRecorder.start(options?.timeslice || 5000);
}
public stop(): Promise<VideoUploadResponse> {
return new Promise((resolve, reject) => {
if (this.getMediaRecorderState() === "inactive") {
reject(new Error("MediaRecorder is already inactive"));
}
this.mediaRecorder.stop();
this.onVideoAvailable = (v) => resolve(v);
this.onStopError = (e) => reject(e);
})
}
public getMediaRecorderState(): RecordingState {
return this.mediaRecorder.state;
}
public pause() {
if (this.getMediaRecorderState() !== "recording") {
throw new Error("MediaRecorder is not recording");
}
this.mediaRecorder.pause();
public stop(): Promise<VideoUploadResponse> {
return new Promise((resolve, reject) => {
if (this.getMediaRecorderState() === "inactive") {
reject(new Error("MediaRecorder is already inactive"));
}
this.mediaRecorder.stop();
this.onVideoAvailable = (v) => resolve(v);
this.onStopError = (e) => reject(e);
});
}
public pause() {
if (this.getMediaRecorderState() !== "recording") {
throw new Error("MediaRecorder is not recording");
}
this.mediaRecorder.pause();
}
public static getSupportedMimeTypes() {
const VIDEO_TYPES = [
"mp4",
"webm",
"ogg",
"x-matroska"
public static getSupportedMimeTypes() {
const VIDEO_TYPES = ["mp4", "webm", "ogg", "x-matroska"];
const VIDEO_CODECS = [
"vp9,opus",
"vp8,opus",
"vp9",
"vp9.0",
"vp8",
"vp8.0",
"h264",
"h.264",
"avc1",
"av1",
"h265",
"h.265",
];
const supportedTypes: string[] = [];
VIDEO_TYPES.forEach((videoType) => {
const type = `video/${videoType}`;
VIDEO_CODECS.forEach((codec) => {
const variations = [
`${type};codecs=${codec}`,
`${type};codecs:${codec}`,
`${type};codecs=${codec.toUpperCase()}`,
`${type};codecs:${codec.toUpperCase()}`,
`${type}`,
];
const VIDEO_CODECS = [
"vp9,opus",
"vp8,opus",
"vp9",
"vp9.0",
"vp8",
"vp8.0",
"h264",
"h.264",
"avc1",
"av1",
"h265",
"h.265",
];
const supportedTypes: string[] = [];
VIDEO_TYPES.forEach((videoType) => {
const type = `video/${videoType}`;
VIDEO_CODECS.forEach((codec) => {
const variations = [
`${type};codecs=${codec}`,
`${type};codecs:${codec}`,
`${type};codecs=${codec.toUpperCase()}`,
`${type};codecs:${codec.toUpperCase()}`,
`${type}`
]
for (const variation of variations) {
if (MediaRecorder.isTypeSupported(variation)) {
supportedTypes.push(variation);
break;
}
}
});
});
return supportedTypes;
}
}
for (const variation of variations) {
if (MediaRecorder.isTypeSupported(variation)) {
supportedTypes.push(variation);
break;
}
}
});
});
return supportedTypes;
}
}

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc