Bump @justinribeiro/lite-youtube from 0.9.0 to 0.9.1 in /build/javascript (#273)

* Commit updated Javascript packages

* Bump preact from 10.5.4 to 10.5.5 in /build/javascript (#265)

* Trying a new github workflow to install javascript packages

* Bump tailwindcss from 1.9.2 to 1.9.4 in /build/javascript (#266)

Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 1.9.2 to 1.9.4.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v1.9.2...v1.9.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Commit updated Javascript packages

* Bump preact from 10.5.4 to 10.5.5 in /build/javascript

Bumps [preact](https://github.com/preactjs/preact) from 10.5.4 to 10.5.5.
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](https://github.com/preactjs/preact/compare/10.5.4...10.5.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Owncast <owncast@owncast.online>

* Bump @justinribeiro/lite-youtube in /build/javascript

Bumps [@justinribeiro/lite-youtube](https://github.com/justinribeiro/lite-youtube) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/justinribeiro/lite-youtube/releases)
- [Commits](https://github.com/justinribeiro/lite-youtube/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>
This commit is contained in:
dependabot[bot]
2020-10-20 15:15:56 -07:00
committed by GitHub
parent fb4a822cd8
commit dab7914eab
6133 changed files with 546543 additions and 1108 deletions

View File

@@ -0,0 +1,55 @@
<a name="3.0.2"></a>
## [3.0.2](https://github.com/videojs/aes-decrypter/compare/v3.0.1...v3.0.2) (2020-09-09)
### Chores
* **package:** update pkcs7 to remove engine check ([062c952](https://github.com/videojs/aes-decrypter/commit/062c952))
<a name="3.0.1"></a>
## [3.0.1](https://github.com/videojs/aes-decrypter/compare/v3.0.0...v3.0.1) (2019-08-21)
### Chores
* **package:** update rollup to version 0.66.0 ([#38](https://github.com/videojs/aes-decrypter/issues/38)) ([634556b](https://github.com/videojs/aes-decrypter/commit/634556b))
* bump videojs-generate-karma-config version ([#51](https://github.com/videojs/aes-decrypter/issues/51)) ([195b923](https://github.com/videojs/aes-decrypter/commit/195b923))
* **package:** update videojs-generate-karma-config to version 5.0.2 ([#57](https://github.com/videojs/aes-decrypter/issues/57)) ([be8bd81](https://github.com/videojs/aes-decrypter/commit/be8bd81))
* update generator version and use [@videojs](https://github.com/videojs)/vhs-utils ([#68](https://github.com/videojs/aes-decrypter/issues/68)) ([9a6ab2f](https://github.com/videojs/aes-decrypter/commit/9a6ab2f))
* Update to generator v7 standards ([#37](https://github.com/videojs/aes-decrypter/issues/37)) ([fcf96c4](https://github.com/videojs/aes-decrypter/commit/fcf96c4))
* Update videojs-generate-karma-config to the latest version 🚀 ([#42](https://github.com/videojs/aes-decrypter/issues/42)) ([2b16de3](https://github.com/videojs/aes-decrypter/commit/2b16de3))
* Update videojs-generate-karma-config to the latest version 🚀 ([#43](https://github.com/videojs/aes-decrypter/issues/43)) ([cb63ccd](https://github.com/videojs/aes-decrypter/commit/cb63ccd))
<a name="3.0.0"></a>
# [3.0.0](https://github.com/videojs/aes-decrypter/compare/v2.0.0...v3.0.0) (2017-07-24)
### Features
* Use Rollup for packaging ([bda57ab](https://github.com/videojs/aes-decrypter/commit/bda57ab))
### Chores
* prepare CHANGELOG for new process ([1a5175c](https://github.com/videojs/aes-decrypter/commit/1a5175c))
### BREAKING CHANGES
* revert to 1.x and stop using web crypto.
## 2.0.0 (2016-11-15)
* Use webcrypto for aes-cbc segment decryption when supported (#4)
* Lock the linter to a specific version
## 1.1.1 (2016-11-17)
* version to revert 1.1.0
## 1.0.3 (2016-06-16)
* dont do browserify-shim globally since we only use it in tests (#1)
## 1.0.2 (2016-06-16)
* specify browserify transform globally
## 1.0.1 (2016-06-16)
* fixing the build pipeline
## 1.0.0 (2016-06-16)
* initial

View File

@@ -0,0 +1,30 @@
# CONTRIBUTING
We welcome contributions from everyone!
## Getting Started
Make sure you have Node.js 4.8 or higher and npm installed.
1. Fork this repository and clone your fork
1. Install dependencies: `npm install`
1. Run a development server: `npm start`
### Making Changes
Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
### Running Tests
Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
- In all available and supported browsers: `npm test`
- In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
- While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
[karma]: http://karma-runner.github.io/
[local]: http://localhost:9999/test/
[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md

13
build/javascript/node_modules/aes-decrypter/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,13 @@
Copyright Brightcove, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

72
build/javascript/node_modules/aes-decrypter/README.md generated vendored Normal file
View File

@@ -0,0 +1,72 @@
# aes-decrypter
[![Build Status](https://travis-ci.org/videojs/aes-decrypter.svg?branch=master)](https://travis-ci.org/videojs/aes-decrypter)
[![Greenkeeper badge](https://badges.greenkeeper.io/videojs/aes-decrypter.svg)](https://greenkeeper.io/)
[![Slack Status](http://slack.videojs.com/badge.svg)](http://slack.videojs.com)
[![NPM](https://nodei.co/npm/aes-decrypter.png?downloads=true&downloadRank=true)](https://nodei.co/npm/aes-decrypter/)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Usage](#usage)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
```sh
npm install --save aes-decrypter
```
Also available to install globally:
```sh
npm install --global aes-decrypter
```
The npm installation is preferred, but Bower works, too.
```sh
bower install --save aes-decrypter
```
## Usage
To include decrypter on your website or npm application, use any of the following methods.
```js
var Decrypter = require('aes-decrypter').Decrypter;
var fs = require('fs');
var keyContent = fs.readFileSync('something.key');
var encryptedBytes = fs.readFileSync('somithing.txt');
// keyContent is a string of the aes-keys content
var keyContent = fs.readFileSync(keyFile);
var view = new DataView(keyContent.buffer);
var key.bytes = new Uint32Array([
view.getUint32(0),
view.getUint32(4),
view.getUint32(8),
view.getUint32(12)
]);
key.iv = new Uint32Array([
0, 0, 0, 0
]);
var d = new Decrypter(
encryptedBytes,
key.bytes,
key.iv,
function(err, decryptedBytes) {
// err always null
});
```
## [License](LICENSE)
Apache-2.0. Copyright (c) Brightcove, Inc.

15
build/javascript/node_modules/aes-decrypter/index.html generated vendored Normal file
View File

@@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>aes-decrypter Demo</title>
<script src="dist/aes-decrypter.js"></script>
</head>
<body>
<p>To test this out, open up your developer console.</p>
<ul>
<li><a href="test/">Run unit tests in browser.</a></li>
<li><a href="docs/api/">Read generated docs.</a></li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,36 @@
<a name="1.3.0"></a>
# [1.3.0](https://github.com/videojs/vhs-utils/compare/v1.2.1...v1.3.0) (2020-02-05)
### Features
* add forEachMediaGroup in media-groups module (#8) ([a1eacf4](https://github.com/videojs/vhs-utils/commit/a1eacf4)), closes [#8](https://github.com/videojs/vhs-utils/issues/8)
<a name="1.2.1"></a>
## [1.2.1](https://github.com/videojs/vhs-utils/compare/v1.2.0...v1.2.1) (2020-01-15)
### Bug Fixes
* include videojs in VHS JSON media type (#7) ([da072f0](https://github.com/videojs/vhs-utils/commit/da072f0)), closes [#7](https://github.com/videojs/vhs-utils/issues/7)
<a name="1.2.0"></a>
# [1.2.0](https://github.com/videojs/vhs-utils/compare/v1.1.0...v1.2.0) (2019-12-06)
### Features
* add media-types module with simpleTypeFromSourceType function (#4) ([d3ebd3f](https://github.com/videojs/vhs-utils/commit/d3ebd3f)), closes [#4](https://github.com/videojs/vhs-utils/issues/4)
* add VHS codec parsing and translation functions (#5) ([4fe0e22](https://github.com/videojs/vhs-utils/commit/4fe0e22)), closes [#5](https://github.com/videojs/vhs-utils/issues/5)
<a name="1.1.0"></a>
# [1.1.0](https://github.com/videojs/stream/compare/v1.0.0...v1.1.0) (2019-08-30)
### Features
* node support and more stream tests ([315ab8d](https://github.com/videojs/stream/commit/315ab8d))
<a name="1.0.0"></a>
# 1.0.0 (2019-08-21)
### Features
* clones from mpd-parser, m3u8-parser, mux.js, aes-decrypter, and vhs ([5e89042](https://github.com/videojs/stream/commit/5e89042))

View File

@@ -0,0 +1,30 @@
# CONTRIBUTING
We welcome contributions from everyone!
## Getting Started
Make sure you have Node.js 8 or higher and npm installed.
1. Fork this repository and clone your fork
1. Install dependencies: `npm install`
1. Run a development server: `npm start`
### Making Changes
Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
### Running Tests
Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
- In all available and supported browsers: `npm test`
- In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
- While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
[karma]: http://karma-runner.github.io/
[local]: http://localhost:9999/test/
[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md

View File

@@ -0,0 +1,19 @@
Copyright (c) brandonocasey <brandonocasey@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,29 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [@videojs/vhs-utils](#videojsvhs-utils)
- [Installation](#installation)
- [Usage](#usage)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# @videojs/vhs-utils
vhs-utils serves two purposes:
1. It extracts objects and functions shared throughout @videojs/http-streaming code to save on package size. See [the original @videojs/http-streaming PR](https://github.com/videojs/http-streaming/pull/637) for details.
2. It exports generic functions from VHS that may be useful to plugin authors.
## Installation
```sh
npm install --save @videojs/vhs-utils
```
## Usage
All utility functions are published under dist and can be required/imported like so:
`import resolveUrl from '@videojs/vhs-utils/dist/resolve-url';`
`const resolveUrl = require('@videojs/vhs-utils/dist/resolve-url');`

View File

@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-stream Demo</title>
<link href="node_modules/video.js/dist/video-js.css" rel="stylesheet">
</head>
<body>
<video id="videojs-stream-player" class="video-js vjs-default-skin" controls>
<source src="//vjs.zencdn.net/v/oceans.mp4" type='video/mp4'>
<source src="//vjs.zencdn.net/v/oceans.webm" type='video/webm'>
</video>
<ul>
<li><a href="/test/debug.html">Run unit tests in browser.</a></li>
</ul>
<script src="node_modules/video.js/dist/video.js"></script>
<script src="dist/videojs-stream.js"></script>
<script>
(function(window, videojs) {
var examplePlayer = window.examplePlayer = videojs('videojs-stream-player');
var stream = window.stream = examplePlayer.stream();
}(window, window.videojs));
</script>
</body>
</html>

View File

@@ -0,0 +1,120 @@
{
"_args": [
[
"@videojs/vhs-utils@1.3.0",
"/home/runner/work/owncast/owncast/build/javascript"
]
],
"_from": "@videojs/vhs-utils@1.3.0",
"_id": "@videojs/vhs-utils@1.3.0",
"_inBundle": false,
"_integrity": "sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==",
"_location": "/aes-decrypter/@videojs/vhs-utils",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@videojs/vhs-utils@1.3.0",
"name": "@videojs/vhs-utils",
"escapedName": "@videojs%2fvhs-utils",
"scope": "@videojs",
"rawSpec": "1.3.0",
"saveSpec": null,
"fetchSpec": "1.3.0"
},
"_requiredBy": [
"/aes-decrypter"
],
"_resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz",
"_spec": "1.3.0",
"_where": "/home/runner/work/owncast/owncast/build/javascript",
"author": {
"name": "brandonocasey",
"email": "brandonocasey@gmail.com"
},
"browserslist": [
"defaults",
"ie 11"
],
"dependencies": {
"@babel/runtime": "^7.5.5",
"global": "^4.3.2",
"url-toolkit": "^2.1.6"
},
"description": "Objects and functions shared throughtout @videojs/http-streaming code",
"devDependencies": {
"@videojs/generator-helpers": "~1.2.0",
"karma": "^4.0.0",
"rollup": "^1.12.0",
"sinon": "^7.2.2",
"videojs-generate-karma-config": "~5.3.0",
"videojs-generate-rollup-config": "~5.0.0",
"videojs-generator-verify": "~1.2.0",
"videojs-standard": "^8.0.3"
},
"engines": {
"node": ">=8",
"npm": ">=5"
},
"files": [
"CONTRIBUTING.md",
"dist/",
"docs/",
"index.html",
"scripts/",
"src/",
"test/"
],
"generator-videojs-plugin": {
"version": "7.7.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"keywords": [
"videojs",
"videojs-plugin"
],
"license": "MIT",
"lint-staged": {
"*.js": [
"vjsstandard --fix",
"git add"
],
"README.md": [
"doctoc --notitle",
"git add"
]
},
"name": "@videojs/vhs-utils",
"scripts": {
"build": "npm-run-all -s clean -p build:*",
"build-prod": "cross-env-shell NO_TEST_BUNDLE=1 'npm run build'",
"build-test": "cross-env-shell TEST_BUNDLE_ONLY=1 'npm run build'",
"build:js": "rollup -c scripts/rollup.config.js",
"clean": "shx rm -rf ./dist ./test/dist && shx mkdir -p ./dist ./test/dist",
"lint": "vjsstandard",
"posttest": "shx cat test/dist/coverage/text.txt",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose --skip-es-check",
"preversion": "npm test",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"start": "npm-run-all -p server watch",
"test": "npm-run-all lint build-test && npm-run-all test:*",
"test:browser": "karma start scripts/karma.conf.js",
"test:node": "qunit test/dist/bundle.js",
"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"watch": "npm-run-all -p watch:*",
"watch:js": "npm run build:js -- -w"
},
"version": "1.3.0",
"vjsstandard": {
"ignore": [
"dist",
"docs",
"test/dist"
]
}
}

View File

@@ -0,0 +1,12 @@
const generate = require('videojs-generate-karma-config');
module.exports = function(config) {
// see https://github.com/videojs/videojs-generate-karma-config
// for options
const options = {};
config = generate(config, options);
// any other custom stuff not supported by options here!
};

View File

@@ -0,0 +1,42 @@
const generate = require('videojs-generate-rollup-config');
const fs = require('fs');
const path = require('path');
const BASE_DIR = path.join(__dirname, '..');
const SRC_DIR = path.join(BASE_DIR, 'src');
const files = fs.readdirSync(SRC_DIR);
const shared = {
externals(defaults) {
defaults.module.push('url-toolkit');
return defaults;
}
};
const builds = [];
files.forEach(function(file, i) {
const config = generate(Object.assign({}, shared, {
input: path.relative(BASE_DIR, path.join(SRC_DIR, file)),
distName: path.basename(file, path.extname(file))
}));
// gaurd against test only builds
if (config.builds.module) {
const module = config.builds.module;
module.output = module.output.filter((o) => o.format === 'cjs');
module.output[0].file = module.output[0].file.replace('.cjs.js', '.js');
builds.push(module);
}
// gaurd against production only builds
// only add the last test bundle we generate as they are all the same
if (i === (files.length - 1) && config.builds.test) {
builds.push(config.builds.test);
}
});
// export the builds to rollup
export default builds;

View File

@@ -0,0 +1,130 @@
/**
* Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
* `avc1.<hhhhhh>`
*
* @param {string} codec
* Codec string to translate
* @return {string}
* The translated codec string
*/
export const translateLegacyCodec = function(codec) {
if (!codec) {
return codec;
}
return codec.replace(/avc1\.(\d+)\.(\d+)/i, function(orig, profile, avcLevel) {
const profileHex = ('00' + Number(profile).toString(16)).slice(-2);
const avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
return 'avc1.' + profileHex + '00' + avcLevelHex;
});
};
/**
* Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
* `avc1.<hhhhhh>`
*
* @param {string[]} codecs
* An array of codec strings to translate
* @return {string[]}
* The translated array of codec strings
*/
export const translateLegacyCodecs = function(codecs) {
return codecs.map(translateLegacyCodec);
};
/**
* Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
* standard `avc1.<hhhhhh>`.
*
* @param {string} codecString
* The codec string
* @return {string}
* The codec string with old apple-style codecs replaced
*
* @private
*/
export const mapLegacyAvcCodecs = function(codecString) {
return codecString.replace(/avc1\.(\d+)\.(\d+)/i, (match) => {
return translateLegacyCodecs([match])[0];
});
};
/**
* @typedef {Object} ParsedCodecInfo
* @property {number} codecCount
* Number of codecs parsed
* @property {string} [videoCodec]
* Parsed video codec (if found)
* @property {string} [videoObjectTypeIndicator]
* Video object type indicator (if found)
* @property {string|null} audioProfile
* Audio profile
*/
/**
* Parses a codec string to retrieve the number of codecs specified, the video codec and
* object type indicator, and the audio profile.
*
* @param {string} [codecs]
* The codec string to parse
* @return {ParsedCodecInfo}
* Parsed codec info
*/
export const parseCodecs = function(codecs = '') {
const result = {
codecCount: 0
};
result.codecCount = codecs.split(',').length;
result.codecCount = result.codecCount || 2;
// parse the video codec
const parsed = (/(^|\s|,)+(avc[13])([^ ,]*)/i).exec(codecs);
if (parsed) {
result.videoCodec = parsed[2];
result.videoObjectTypeIndicator = parsed[3];
}
// parse the last field of the audio codec
result.audioProfile =
(/(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i).exec(codecs);
result.audioProfile = result.audioProfile && result.audioProfile[2];
return result;
};
/**
* Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
* a default alternate audio playlist for the provided audio group.
*
* @param {Object} master
* The master playlist
* @param {string} audioGroupId
* ID of the audio group for which to find the default codec info
* @return {ParsedCodecInfo}
* Parsed codec info
*/
export const audioProfileFromDefault = (master, audioGroupId) => {
if (!master.mediaGroups.AUDIO || !audioGroupId) {
return null;
}
const audioGroup = master.mediaGroups.AUDIO[audioGroupId];
if (!audioGroup) {
return null;
}
for (const name in audioGroup) {
const audioType = audioGroup[name];
if (audioType.default && audioType.playlists) {
// codec should be the same for all playlists within the audio type
return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
}
}
return null;
};

View File

@@ -0,0 +1,13 @@
import window from 'global/window';
const atob = (s) => window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
export default function decodeB64ToUint8Array(b64Text) {
const decodedString = atob(b64Text);
const array = new Uint8Array(decodedString.length);
for (let i = 0; i < decodedString.length; i++) {
array[i] = decodedString.charCodeAt(i);
}
return array;
}

View File

@@ -0,0 +1,22 @@
/**
* Loops through all supported media groups in master and calls the provided
* callback for each group
*
* @param {Object} master
* The parsed master manifest object
* @param {string[]} groups
* The media groups to call the callback for
* @param {Function} callback
* Callback to call for each media group
*/
export const forEachMediaGroup = (master, groups, callback) => {
groups.forEach((mediaType) => {
for (const groupKey in master.mediaGroups[mediaType]) {
for (const labelKey in master.mediaGroups[mediaType][groupKey]) {
const mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
callback(mediaProperties, mediaType, groupKey, labelKey);
}
}
});
};

View File

@@ -0,0 +1,36 @@
const MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
const DASH_REGEX = /^application\/dash\+xml/i;
/**
* Returns a string that describes the type of source based on a video source object's
* media type.
*
* @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
*
* @param {string} type
* Video source object media type
* @return {('hls'|'dash'|'vhs-json'|null)}
* VHS source type string
*/
export const simpleTypeFromSourceType = (type) => {
if (MPEGURL_REGEX.test(type)) {
return 'hls';
}
if (DASH_REGEX.test(type)) {
return 'dash';
}
// Denotes the special case of a manifest object passed to http-streaming instead of a
// source URL.
//
// See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
//
// In this case, vnd stands for vendor, video.js for the organization, VHS for this
// project, and the +json suffix identifies the structure of the media type.
if (type === 'application/vnd.videojs.vhs+json') {
return 'vhs-json';
}
return null;
};

View File

@@ -0,0 +1,18 @@
import URLToolkit from 'url-toolkit';
import window from 'global/window';
const resolveUrl = (baseUrl, relativeUrl) => {
// return early if we don't need to resolve
if ((/^[a-z]+:/i).test(relativeUrl)) {
return relativeUrl;
}
// if the base URL is relative then combine with the current location
if (!(/\/\//i).test(baseUrl)) {
baseUrl = URLToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
}
return URLToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
};
export default resolveUrl;

View File

@@ -0,0 +1,108 @@
/**
* @file stream.js
*/
/**
* A lightweight readable stream implemention that handles event dispatching.
*
* @class Stream
*/
export default class Stream {
constructor() {
this.listeners = {};
}
/**
* Add a listener for a specified event type.
*
* @param {string} type the event name
* @param {Function} listener the callback to be invoked when an event of
* the specified type occurs
*/
on(type, listener) {
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
}
/**
* Remove a listener for a specified event type.
*
* @param {string} type the event name
* @param {Function} listener a function previously registered for this
* type of event through `on`
* @return {boolean} if we could turn it off or not
*/
off(type, listener) {
if (!this.listeners[type]) {
return false;
}
const index = this.listeners[type].indexOf(listener);
// TODO: which is better?
// In Video.js we slice listener functions
// on trigger so that it does not mess up the order
// while we loop through.
//
// Here we slice on off so that the loop in trigger
// can continue using it's old reference to loop without
// messing up the order.
this.listeners[type] = this.listeners[type].slice(0);
this.listeners[type].splice(index, 1);
return index > -1;
}
/**
* Trigger an event of the specified type on this stream. Any additional
* arguments to this function are passed as parameters to event listeners.
*
* @param {string} type the event name
*/
trigger(type) {
const callbacks = this.listeners[type];
if (!callbacks) {
return;
}
// Slicing the arguments on every invocation of this method
// can add a significant amount of overhead. Avoid the
// intermediate object creation for the common case of a
// single callback argument
if (arguments.length === 2) {
const length = callbacks.length;
for (let i = 0; i < length; ++i) {
callbacks[i].call(this, arguments[1]);
}
} else {
const args = Array.prototype.slice.call(arguments, 1);
const length = callbacks.length;
for (let i = 0; i < length; ++i) {
callbacks[i].apply(this, args);
}
}
}
/**
* Destroys the stream and cleans up.
*/
dispose() {
this.listeners = {};
}
/**
* Forwards all `data` events on this stream to the destination stream. The
* destination stream should provide a method `push` to receive the data
* events as they arrive.
*
* @param {Stream} destination the stream that will receive all `data` events
* @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
*/
pipe(destination) {
this.on('data', function(data) {
destination.push(data);
});
}
}

View File

@@ -0,0 +1,242 @@
import QUnit from 'qunit';
import {
mapLegacyAvcCodecs,
translateLegacyCodecs,
parseCodecs,
audioProfileFromDefault
} from '../src/codecs';
QUnit.module('Legacy Codecs');
QUnit.test('maps legacy AVC codecs', function(assert) {
assert.equal(
mapLegacyAvcCodecs('avc1.deadbeef'),
'avc1.deadbeef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.dead.beef, mp4a.something'),
'avc1.dead.beef, mp4a.something',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.dead.beef,mp4a.something'),
'avc1.dead.beef,mp4a.something',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.something,avc1.dead.beef'),
'mp4a.something,avc1.dead.beef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.something, avc1.dead.beef'),
'mp4a.something, avc1.dead.beef',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.42001e'),
'avc1.42001e',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('avc1.4d0020,mp4a.40.2'),
'avc1.4d0020,mp4a.40.2',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.2,avc1.4d0020'),
'mp4a.40.2,avc1.4d0020',
'does nothing for non legacy pattern'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.40'),
'mp4a.40.40',
'does nothing for non video codecs'
);
assert.equal(
mapLegacyAvcCodecs('avc1.66.30'),
'avc1.42001e',
'translates legacy video codec alone'
);
assert.equal(
mapLegacyAvcCodecs('avc1.66.30, mp4a.40.2'),
'avc1.42001e, mp4a.40.2',
'translates legacy video codec when paired with audio'
);
assert.equal(
mapLegacyAvcCodecs('mp4a.40.2, avc1.66.30'),
'mp4a.40.2, avc1.42001e',
'translates video codec when specified second'
);
});
QUnit.test('translates legacy codecs', function(assert) {
assert.deepEqual(
translateLegacyCodecs(['avc1.66.30', 'avc1.66.30']),
['avc1.42001e', 'avc1.42001e'],
'translates legacy avc1.66.30 codec'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.42C01E', 'avc1.42C01E']),
['avc1.42C01E', 'avc1.42C01E'],
'does not translate modern codecs'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.42C01E', 'avc1.66.30']),
['avc1.42C01E', 'avc1.42001e'],
'only translates legacy codecs when mixed'
);
assert.deepEqual(
translateLegacyCodecs(['avc1.4d0020', 'avc1.100.41', 'avc1.77.41',
'avc1.77.32', 'avc1.77.31', 'avc1.77.30',
'avc1.66.30', 'avc1.66.21', 'avc1.42C01e']),
['avc1.4d0020', 'avc1.640029', 'avc1.4d0029',
'avc1.4d0020', 'avc1.4d001f', 'avc1.4d001e',
'avc1.42001e', 'avc1.420015', 'avc1.42C01e'],
'translates a whole bunch'
);
});
QUnit.module('parseCodecs');
QUnit.test('parses video only codec string', function(assert) {
assert.deepEqual(
parseCodecs('avc1.42001e'),
{
codecCount: 1,
videoCodec: 'avc1',
videoObjectTypeIndicator: '.42001e',
audioProfile: null
},
'parsed video only codec string'
);
});
QUnit.test('parses audio only codec string', function(assert) {
assert.deepEqual(
parseCodecs('mp4a.40.2'),
{
codecCount: 1,
audioProfile: '2'
},
'parsed audio only codec string'
);
});
QUnit.test('parses video and audio codec string', function(assert) {
assert.deepEqual(
parseCodecs('avc1.42001e, mp4a.40.2'),
{
codecCount: 2,
videoCodec: 'avc1',
videoObjectTypeIndicator: '.42001e',
audioProfile: '2'
},
'parsed video and audio codec string'
);
});
QUnit.module('audioProfileFromDefault');
QUnit.test('returns falsey when no audio group ID', function(assert) {
assert.notOk(
audioProfileFromDefault(
{ mediaGroups: { AUDIO: {} } },
'',
),
'returns falsey when no audio group ID'
);
});
QUnit.test('returns falsey when no matching audio group', function(assert) {
assert.notOk(
audioProfileFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: true,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au2'
),
'returned falsey when no matching audio group'
);
});
QUnit.test('returns falsey when no default for audio group', function(assert) {
assert.notOk(
audioProfileFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au1'
),
'returned falsey when no default for audio group'
);
});
QUnit.test('returns audio profile for default in audio group', function(assert) {
assert.deepEqual(
audioProfileFromDefault(
{
mediaGroups: {
AUDIO: {
au1: {
en: {
default: false,
playlists: [{
attributes: { CODECS: 'mp4a.40.2' }
}]
},
es: {
default: true,
playlists: [{
attributes: { CODECS: 'mp4a.40.5' }
}]
}
}
}
}
},
'au1'
),
'5',
'returned parsed codec audio profile'
);
});

View File

@@ -0,0 +1,13 @@
import QUnit from 'qunit';
import decodeB64ToUint8Array from '../src/decode-b64-to-uint8-array.js';
QUnit.module('decodeB64ToUint8Array');
// slightly modified version of m3u8 test
// 'parses Widevine #EXT-X-KEY attributes and attaches to manifest'
QUnit.test('can decode', function(assert) {
const b64 = 'AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnNoYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY';
const result = decodeB64ToUint8Array(b64);
assert.deepEqual(result.byteLength, 62, 'decoded');
});

View File

@@ -0,0 +1,171 @@
import QUnit from 'qunit';
import { forEachMediaGroup } from '../src/media-groups';
QUnit.module('forEachMediaGroup');
QUnit.test('runs callback for each audio media group', function(assert) {
const master = {
mediaGroups: {
AUDIO: {
au1: {
en: { en: 'en' },
es: { es: 'es' }
},
au2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' }
],
'iterated audio media groups'
);
});
QUnit.test('runs callback for each subtitle media group', function(assert) {
const master = {
mediaGroups: {
SUBTITLES: {
sub1: {
en: { en: 'en' },
es: { es: 'es' }
},
sub2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' }
],
'iterated subtitles media groups'
);
});
QUnit.test('runs callback for each audio and subtitles media group', function(assert) {
const master = {
mediaGroups: {
AUDIO: {
au1: {
en: { en: 'en' },
es: { es: 'es' }
},
au2: {
de: { de: 'de' },
fr: { fr: 'fr' }
}
},
SUBTITLES: {
sub1: {
enS: { enS: 'enS' },
esS: { esS: 'esS' }
},
sub2: {
deS: { deS: 'deS' },
frS: { frS: 'frS' }
}
},
OTHER: {
other1: {
other11: { other11: 'other11' },
other12: { other12: 'other12' }
},
other2: {
other21: { other21: 'other21' },
other22: { other22: 'other22' }
}
}
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO', 'SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(
iteratedMediaGroups,
[
{ en: 'en' },
{ es: 'es' },
{ de: 'de' },
{ fr: 'fr' },
{ enS: 'enS' },
{ esS: 'esS' },
{ deS: 'deS' },
{ frS: 'frS' }
],
'iterated audio and subtitles media groups'
);
});
QUnit.test('does not run callback for non specified media groups', function(assert) {
const master = {
mediaGroups: {
'VIDEO': { v1: { en: { en: 'en' } } },
'CLOSED-CAPTIONS': { cc1: { en: { en: 'en' } } }
}
};
const iteratedMediaGroups = [];
forEachMediaGroup(
master,
['AUDIO', 'SUBTITLES'],
(mediaGroup) => iteratedMediaGroups.push(mediaGroup)
);
assert.deepEqual(iteratedMediaGroups, [], 'did not iterate non specified media groups');
});

View File

@@ -0,0 +1,41 @@
import QUnit from 'qunit';
import { simpleTypeFromSourceType } from '../src/media-types';
QUnit.module('simpleTypeFromSourceType');
QUnit.test('simpleTypeFromSourceType converts HLS mime types to hls', function(assert) {
assert.equal(
simpleTypeFromSourceType('aPplicatiOn/x-MPegUrl'),
'hls',
'supports application/x-mpegurl'
);
assert.equal(
simpleTypeFromSourceType('aPplicatiOn/VnD.aPPle.MpEgUrL'),
'hls',
'supports application/vnd.apple.mpegurl'
);
});
QUnit.test('simpleTypeFromSourceType converts DASH mime type to dash', function(assert) {
assert.equal(
simpleTypeFromSourceType('aPplication/dAsh+xMl'),
'dash',
'supports application/dash+xml'
);
});
QUnit.test(
'simpleTypeFromSourceType does not convert non HLS/DASH mime types',
function(assert) {
assert.notOk(simpleTypeFromSourceType('video/mp4'), 'does not support video/mp4');
assert.notOk(simpleTypeFromSourceType('video/x-flv'), 'does not support video/x-flv');
}
);
QUnit.test('simpleTypeFromSourceType converts VHS media type to vhs-json', function(assert) {
assert.equal(
simpleTypeFromSourceType('application/vnd.videojs.vhs+json'),
'vhs-json',
'supports application/vnd.videojs.vhs+json'
);
});

View File

@@ -0,0 +1,28 @@
import QUnit from 'qunit';
import window from 'global/window';
import resolveUrl from '../src/resolve-url';
// A modified subset of tests from https://github.com/tjenkinson/url-toolkit
QUnit.module('URL resolver');
QUnit.test('works with a selection of valid urls', function(assert) {
let currentLocation = '';
if (window.location && window.location.protocol) {
currentLocation = window.location.protocol + '//' + window.location.host;
}
assert.equal(
resolveUrl('http://a.com/b/cd/e.m3u8', 'https://example.com/z.ts'),
'https://example.com/z.ts'
);
assert.equal(resolveUrl('http://a.com/b/cd/e.m3u8', 'z.ts'), 'http://a.com/b/cd/z.ts');
assert.equal(resolveUrl('//a.com/b/cd/e.m3u8', 'z.ts'), '//a.com/b/cd/z.ts');
assert.equal(
resolveUrl('/a/b/cd/e.m3u8', 'https://example.com:8080/z.ts'),
'https://example.com:8080/z.ts'
);
assert.equal(resolveUrl('/a/b/cd/e.m3u8', 'z.ts'), currentLocation + '/a/b/cd/z.ts');
assert.equal(resolveUrl('/a/b/cd/e.m3u8', '../../../z.ts'), currentLocation + '/z.ts');
});

View File

@@ -0,0 +1,51 @@
import QUnit from 'qunit';
import Stream from '../src/stream';
QUnit.module('stream', {
beforeEach() {
this.stream = new Stream();
},
afterEach() {
this.stream.dispose();
}
});
QUnit.test('trigger calls listeners', function(assert) {
const args = [];
this.stream.on('test', function(data) {
args.push(data);
});
this.stream.trigger('test', 1);
this.stream.trigger('test', 2);
assert.deepEqual(args, [1, 2]);
});
QUnit.test('callbacks can remove themselves', function(assert) {
const args1 = [];
const args2 = [];
const args3 = [];
const arg2Fn = (event) => {
args2.push(event);
this.stream.off('test', arg2Fn);
};
this.stream.on('test', (event) => {
args1.push(event);
});
this.stream.on('test', arg2Fn);
this.stream.on('test', (event) => {
args3.push(event);
});
this.stream.trigger('test', 1);
this.stream.trigger('test', 2);
assert.deepEqual(args1, [1, 2], 'first callback ran all times');
assert.deepEqual(args2, [1], 'second callback removed after first run');
assert.deepEqual(args3, [1, 2], 'third callback ran all times');
});

View File

@@ -0,0 +1,144 @@
{
"_args": [
[
"aes-decrypter@3.0.2",
"/home/runner/work/owncast/owncast/build/javascript"
]
],
"_from": "aes-decrypter@3.0.2",
"_id": "aes-decrypter@3.0.2",
"_inBundle": false,
"_integrity": "sha512-SBAfPQpGTbHbAR6qSybPjMio+MYuZwdD/a/ltOq6hj53vK94dphAm88rR3FNaZyEa/x3SobYmdT5f46qUwroLQ==",
"_location": "/aes-decrypter",
"_phantomChildren": {
"@babel/runtime": "7.11.2",
"global": "4.4.0",
"url-toolkit": "2.2.0"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "aes-decrypter@3.0.2",
"name": "aes-decrypter",
"escapedName": "aes-decrypter",
"rawSpec": "3.0.2",
"saveSpec": null,
"fetchSpec": "3.0.2"
},
"_requiredBy": [
"/@videojs/http-streaming"
],
"_resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.0.2.tgz",
"_spec": "3.0.2",
"_where": "/home/runner/work/owncast/owncast/build/javascript",
"author": {
"name": "Brightcove, Inc."
},
"browserslist": [
"defaults",
"ie 11"
],
"bugs": {
"url": "https://github.com/videojs/aes-decrypter/issues"
},
"contributors": [
{
"name": "gkatsev"
},
{
"name": "imbcmdth"
},
{
"name": "dmlap"
},
{
"name": "bcasey"
}
],
"dependencies": {
"@babel/runtime": "^7.5.5",
"@videojs/vhs-utils": "^1.0.0",
"global": "^4.3.2",
"pkcs7": "^1.0.4"
},
"description": "decrypt aes-128 content using a key",
"devDependencies": {
"@videojs/generator-helpers": "~1.2.0",
"karma": "^4.0.0",
"rollup": "^1.19.4",
"sinon": "^7.2.2",
"videojs-generate-karma-config": "~5.3.1",
"videojs-generate-rollup-config": "~5.0.1",
"videojs-generator-verify": "~2.0.0",
"videojs-standard": "^8.0.3"
},
"directories": {
"test": "test"
},
"files": [
"CONTRIBUTING.md",
"dist/",
"docs/",
"index.html",
"scripts/",
"src/",
"test/"
],
"generator-videojs-plugin": {
"version": "7.7.3"
},
"homepage": "https://github.com/videojs/aes-decrypter#readme",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"keywords": [
"videojs",
"videojs-plugin"
],
"license": "Apache-2.0",
"lint-staged": {
"*.js": [
"vjsstandard --fix",
"git add"
],
"README.md": [
"doctoc --notitle",
"git add"
]
},
"main": "dist/aes-decrypter.cjs.js",
"module": "dist/aes-decrypter.es.js",
"name": "aes-decrypter",
"repository": {
"type": "git",
"url": "git+https://github.com/videojs/aes-decrypter.git"
},
"scripts": {
"build": "npm-run-all -s clean -p build:*",
"build-prod": "cross-env-shell NO_TEST_BUNDLE=1 'npm run build'",
"build-test": "cross-env-shell TEST_BUNDLE_ONLY=1 'npm run build'",
"build:js": "rollup -c scripts/rollup.config.js",
"clean": "shx rm -rf ./dist ./test/dist && shx mkdir -p ./dist ./test/dist",
"lint": "vjsstandard",
"posttest": "shx cat test/dist/coverage/text.txt",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose",
"preversion": "npm test",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"start": "npm-run-all -p server watch",
"test": "npm-run-all lint build-test && karma start scripts/karma.conf.js",
"update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
"version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
"watch": "npm-run-all -p watch:*",
"watch:js": "npm run build:js -- -w"
},
"version": "3.0.2",
"vjsstandard": {
"ignore": [
"dist",
"docs",
"test/dist"
]
}
}

View File

@@ -0,0 +1,12 @@
const generate = require('videojs-generate-karma-config');
module.exports = function(config) {
// see https://github.com/videojs/videojs-generate-karma-config
// for options
const options = {};
config = generate(config, options);
// any other custom stuff not supported by options here!
};

View File

@@ -0,0 +1,18 @@
const generate = require('videojs-generate-rollup-config');
// see https://github.com/videojs/videojs-generate-rollup-config
// for options
const options = {
input: 'src/index.js',
externals(defaults) {
defaults.module.push('pkcs7');
defaults.module.push('@videojs/vhs-utils');
return defaults;
}
};
const config = generate(options);
// Add additonal builds/customization here!
// export the builds to rollup
export default Object.values(config.builds);

258
build/javascript/node_modules/aes-decrypter/src/aes.js generated vendored Normal file
View File

@@ -0,0 +1,258 @@
/**
* @file aes.js
*
* This file contains an adaptation of the AES decryption algorithm
* from the Standford Javascript Cryptography Library. That work is
* covered by the following copyright and permissions notice:
*
* Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the authors.
*/
/**
* Expand the S-box tables.
*
* @private
*/
const precompute = function() {
const tables = [[[], [], [], [], []], [[], [], [], [], []]];
const encTable = tables[0];
const decTable = tables[1];
const sbox = encTable[4];
const sboxInv = decTable[4];
let i;
let x;
let xInv;
const d = [];
const th = [];
let x2;
let x4;
let x8;
let s;
let tEnc;
let tDec;
// Compute double and third tables
for (i = 0; i < 256; i++) {
th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
}
for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
// Compute sbox
s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
s = s >> 8 ^ s & 255 ^ 99;
sbox[x] = s;
sboxInv[s] = x;
// Compute MixColumns
x8 = d[x4 = d[x2 = d[x]]];
tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
tEnc = d[s] * 0x101 ^ s * 0x1010100;
for (i = 0; i < 4; i++) {
encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
}
}
// Compactify. Considerable speedup on Firefox.
for (i = 0; i < 5; i++) {
encTable[i] = encTable[i].slice(0);
decTable[i] = decTable[i].slice(0);
}
return tables;
};
let aesTables = null;
/**
* Schedule out an AES key for both encryption and decryption. This
* is a low-level class. Use a cipher mode to do bulk encryption.
*
* @class AES
* @param key {Array} The key as an array of 4, 6 or 8 words.
*/
export default class AES {
constructor(key) {
/**
* The expanded S-box and inverse S-box tables. These will be computed
* on the client so that we don't have to send them down the wire.
*
* There are two tables, _tables[0] is for encryption and
* _tables[1] is for decryption.
*
* The first 4 sub-tables are the expanded S-box with MixColumns. The
* last (_tables[01][4]) is the S-box itself.
*
* @private
*/
// if we have yet to precompute the S-box tables
// do so now
if (!aesTables) {
aesTables = precompute();
}
// then make a copy of that object for use
this._tables = [[aesTables[0][0].slice(),
aesTables[0][1].slice(),
aesTables[0][2].slice(),
aesTables[0][3].slice(),
aesTables[0][4].slice()],
[aesTables[1][0].slice(),
aesTables[1][1].slice(),
aesTables[1][2].slice(),
aesTables[1][3].slice(),
aesTables[1][4].slice()]];
let i;
let j;
let tmp;
const sbox = this._tables[0][4];
const decTable = this._tables[1];
const keyLen = key.length;
let rcon = 1;
if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
throw new Error('Invalid aes key size');
}
const encKey = key.slice(0);
const decKey = [];
this._key = [encKey, decKey];
// schedule encryption keys
for (i = keyLen; i < 4 * keyLen + 28; i++) {
tmp = encKey[i - 1];
// apply sbox
if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
tmp = sbox[tmp >>> 24] << 24 ^
sbox[tmp >> 16 & 255] << 16 ^
sbox[tmp >> 8 & 255] << 8 ^
sbox[tmp & 255];
// shift rows and add rcon
if (i % keyLen === 0) {
tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
rcon = rcon << 1 ^ (rcon >> 7) * 283;
}
}
encKey[i] = encKey[i - keyLen] ^ tmp;
}
// schedule decryption keys
for (j = 0; i; j++, i--) {
tmp = encKey[j & 3 ? i : i - 4];
if (i <= 4 || j < 4) {
decKey[j] = tmp;
} else {
decKey[j] = decTable[0][sbox[tmp >>> 24 ]] ^
decTable[1][sbox[tmp >> 16 & 255]] ^
decTable[2][sbox[tmp >> 8 & 255]] ^
decTable[3][sbox[tmp & 255]];
}
}
}
/**
* Decrypt 16 bytes, specified as four 32-bit words.
*
* @param {number} encrypted0 the first word to decrypt
* @param {number} encrypted1 the second word to decrypt
* @param {number} encrypted2 the third word to decrypt
* @param {number} encrypted3 the fourth word to decrypt
* @param {Int32Array} out the array to write the decrypted words
* into
* @param {number} offset the offset into the output array to start
* writing results
* @return {Array} The plaintext.
*/
decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
const key = this._key[1];
// state variables a,b,c,d are loaded with pre-whitened data
let a = encrypted0 ^ key[0];
let b = encrypted3 ^ key[1];
let c = encrypted2 ^ key[2];
let d = encrypted1 ^ key[3];
let a2;
let b2;
let c2;
// key.length === 2 ?
const nInnerRounds = key.length / 4 - 2;
let i;
let kIndex = 4;
const table = this._tables[1];
// load up the tables
const table0 = table[0];
const table1 = table[1];
const table2 = table[2];
const table3 = table[3];
const sbox = table[4];
// Inner rounds. Cribbed from OpenSSL.
for (i = 0; i < nInnerRounds; i++) {
a2 = table0[a >>> 24] ^
table1[b >> 16 & 255] ^
table2[c >> 8 & 255] ^
table3[d & 255] ^
key[kIndex];
b2 = table0[b >>> 24] ^
table1[c >> 16 & 255] ^
table2[d >> 8 & 255] ^
table3[a & 255] ^
key[kIndex + 1];
c2 = table0[c >>> 24] ^
table1[d >> 16 & 255] ^
table2[a >> 8 & 255] ^
table3[b & 255] ^
key[kIndex + 2];
d = table0[d >>> 24] ^
table1[a >> 16 & 255] ^
table2[b >> 8 & 255] ^
table3[c & 255] ^
key[kIndex + 3];
kIndex += 4;
a = a2; b = b2; c = c2;
}
// Last round.
for (i = 0; i < 4; i++) {
out[(3 & -i) + offset] =
sbox[a >>> 24] << 24 ^
sbox[b >> 16 & 255] << 16 ^
sbox[c >> 8 & 255] << 8 ^
sbox[d & 255] ^
key[kIndex++];
a2 = a; a = b; b = c; c = d; d = a2;
}
}
}

View File

@@ -0,0 +1,53 @@
/**
* @file async-stream.js
*/
import Stream from '@videojs/vhs-utils/dist/stream.js';
/**
* A wrapper around the Stream class to use setTimeout
* and run stream "jobs" Asynchronously
*
* @class AsyncStream
* @extends Stream
*/
export default class AsyncStream extends Stream {
constructor() {
super(Stream);
this.jobs = [];
this.delay = 1;
this.timeout_ = null;
}
/**
* process an async job
*
* @private
*/
processJob_() {
this.jobs.shift()();
if (this.jobs.length) {
this.timeout_ = setTimeout(
this.processJob_.bind(this),
this.delay
);
} else {
this.timeout_ = null;
}
}
/**
* push a job into the stream
*
* @param {Function} job the job to push into the stream
*/
push(job) {
this.jobs.push(job);
if (!this.timeout_) {
this.timeout_ = setTimeout(
this.processJob_.bind(this),
this.delay
);
}
}
}

View File

@@ -0,0 +1,179 @@
/**
* @file decrypter.js
*
* An asynchronous implementation of AES-128 CBC decryption with
* PKCS#7 padding.
*/
import AES from './aes';
import AsyncStream from './async-stream';
import {unpad} from 'pkcs7';
/**
* Convert network-order (big-endian) bytes into their little-endian
* representation.
*/
const ntoh = function(word) {
return (word << 24) |
((word & 0xff00) << 8) |
((word & 0xff0000) >> 8) |
(word >>> 24);
};
/**
* Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
*
* @param {Uint8Array} encrypted the encrypted bytes
* @param {Uint32Array} key the bytes of the decryption key
* @param {Uint32Array} initVector the initialization vector (IV) to
* use for the first round of CBC.
* @return {Uint8Array} the decrypted bytes
*
* @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
* @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
* @see https://tools.ietf.org/html/rfc2315
*/
const decrypt = function(encrypted, key, initVector) {
// word-level access to the encrypted bytes
const encrypted32 = new Int32Array(
encrypted.buffer,
encrypted.byteOffset,
encrypted.byteLength >> 2
);
const decipher = new AES(Array.prototype.slice.call(key));
// byte and word-level access for the decrypted output
const decrypted = new Uint8Array(encrypted.byteLength);
const decrypted32 = new Int32Array(decrypted.buffer);
// temporary variables for working with the IV, encrypted, and
// decrypted data
let init0;
let init1;
let init2;
let init3;
let encrypted0;
let encrypted1;
let encrypted2;
let encrypted3;
// iteration variable
let wordIx;
// pull out the words of the IV to ensure we don't modify the
// passed-in reference and easier access
init0 = initVector[0];
init1 = initVector[1];
init2 = initVector[2];
init3 = initVector[3];
// decrypt four word sequences, applying cipher-block chaining (CBC)
// to each decrypted block
for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
// convert big-endian (network order) words into little-endian
// (javascript order)
encrypted0 = ntoh(encrypted32[wordIx]);
encrypted1 = ntoh(encrypted32[wordIx + 1]);
encrypted2 = ntoh(encrypted32[wordIx + 2]);
encrypted3 = ntoh(encrypted32[wordIx + 3]);
// decrypt the block
decipher.decrypt(
encrypted0,
encrypted1,
encrypted2,
encrypted3,
decrypted32,
wordIx
);
// XOR with the IV, and restore network byte-order to obtain the
// plaintext
decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
// setup the IV for the next round
init0 = encrypted0;
init1 = encrypted1;
init2 = encrypted2;
init3 = encrypted3;
}
return decrypted;
};
/**
* The `Decrypter` class that manages decryption of AES
* data through `AsyncStream` objects and the `decrypt`
* function
*
* @param {Uint8Array} encrypted the encrypted bytes
* @param {Uint32Array} key the bytes of the decryption key
* @param {Uint32Array} initVector the initialization vector (IV) to
* @param {Function} done the function to run when done
* @class Decrypter
*/
class Decrypter {
constructor(encrypted, key, initVector, done) {
const step = Decrypter.STEP;
const encrypted32 = new Int32Array(encrypted.buffer);
const decrypted = new Uint8Array(encrypted.byteLength);
let i = 0;
this.asyncStream_ = new AsyncStream();
// split up the encryption job and do the individual chunks asynchronously
this.asyncStream_.push(this.decryptChunk_(
encrypted32.subarray(i, i + step),
key,
initVector,
decrypted
));
for (i = step; i < encrypted32.length; i += step) {
initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
ntoh(encrypted32[i - 3]),
ntoh(encrypted32[i - 2]),
ntoh(encrypted32[i - 1])]);
this.asyncStream_.push(this.decryptChunk_(
encrypted32.subarray(i, i + step),
key,
initVector,
decrypted
));
}
// invoke the done() callback when everything is finished
this.asyncStream_.push(function() {
// remove pkcs#7 padding from the decrypted bytes
done(null, unpad(decrypted));
});
}
/**
* a getter for step the maximum number of bytes to process at one time
*
* @return {number} the value of step 32000
*/
static get STEP() {
// 4 * 8000;
return 32000;
}
/**
* @private
*/
decryptChunk_(encrypted, key, initVector, decrypted) {
return function() {
const bytes = decrypt(encrypted, key, initVector);
decrypted.set(bytes, encrypted.byteOffset);
};
}
}
export {
Decrypter,
decrypt
};

View File

@@ -0,0 +1,18 @@
/**
* @file index.js
*
* Index module to easily import the primary components of AES-128
* decryption. Like this:
*
* ```js
* import {Decrypter, decrypt, AsyncStream} from 'aes-decrypter';
* ```
*/
import {decrypt, Decrypter} from './decrypter';
import AsyncStream from './async-stream';
export {
decrypt,
Decrypter,
AsyncStream
};

View File

@@ -0,0 +1,198 @@
// see docs/hlse.md for instructions on how test data was generated
import QUnit from 'qunit';
import {unpad} from 'pkcs7';
import sinon from 'sinon';
import {decrypt, Decrypter, AsyncStream} from '../src';
// see docs/hlse.md for instructions on how test data was generated
const stringFromBytes = function(bytes) {
let result = '';
for (let i = 0; i < bytes.length; i++) {
result += String.fromCharCode(bytes[i]);
}
return result;
};
QUnit.module('Decryption');
QUnit.test('decrypts a single AES-128 with PKCS7 block', function(assert) {
const key = new Uint32Array([0, 0, 0, 0]);
const initVector = key;
// the string "howdy folks" encrypted
const encrypted = new Uint8Array([
0xce, 0x90, 0x97, 0xd0,
0x08, 0x46, 0x4d, 0x18,
0x4f, 0xae, 0x01, 0x1c,
0x82, 0xa8, 0xf0, 0x67
]);
assert.deepEqual(
'howdy folks',
stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
'decrypted with a byte array key'
);
});
QUnit.test('decrypts multiple AES-128 blocks with CBC', function(assert) {
const key = new Uint32Array([0, 0, 0, 0]);
const initVector = key;
// the string "0123456789abcdef01234" encrypted
const encrypted = new Uint8Array([
0x14, 0xf5, 0xfe, 0x74,
0x69, 0x66, 0xf2, 0x92,
0x65, 0x1c, 0x22, 0x88,
0xbb, 0xff, 0x46, 0x09,
0x0b, 0xde, 0x5e, 0x71,
0x77, 0x87, 0xeb, 0x84,
0xa9, 0x54, 0xc2, 0x45,
0xe9, 0x4e, 0x29, 0xb3
]);
assert.deepEqual(
'0123456789abcdef01234',
stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
'decrypted multiple blocks'
);
});
QUnit.test(
'verify that the deepcopy works by doing two decrypts in the same test',
function(assert) {
const key = new Uint32Array([0, 0, 0, 0]);
const initVector = key;
// the string "howdy folks" encrypted
const pkcs7Block = new Uint8Array([
0xce, 0x90, 0x97, 0xd0,
0x08, 0x46, 0x4d, 0x18,
0x4f, 0xae, 0x01, 0x1c,
0x82, 0xa8, 0xf0, 0x67
]);
assert.deepEqual(
'howdy folks',
stringFromBytes(unpad(decrypt(pkcs7Block, key, initVector))),
'decrypted with a byte array key'
);
// the string "0123456789abcdef01234" encrypted
const cbcBlocks = new Uint8Array([
0x14, 0xf5, 0xfe, 0x74,
0x69, 0x66, 0xf2, 0x92,
0x65, 0x1c, 0x22, 0x88,
0xbb, 0xff, 0x46, 0x09,
0x0b, 0xde, 0x5e, 0x71,
0x77, 0x87, 0xeb, 0x84,
0xa9, 0x54, 0xc2, 0x45,
0xe9, 0x4e, 0x29, 0xb3
]);
assert.deepEqual(
'0123456789abcdef01234',
stringFromBytes(unpad(decrypt(cbcBlocks, key, initVector))),
'decrypted multiple blocks'
);
}
);
QUnit.module('Incremental Processing', {
beforeEach() {
this.clock = sinon.useFakeTimers();
},
afterEach() {
this.clock.restore();
}
});
QUnit.test('executes a callback after a timeout', function(assert) {
const asyncStream = new AsyncStream();
let calls = '';
asyncStream.push(function() {
calls += 'a';
});
this.clock.tick(asyncStream.delay);
assert.equal(calls, 'a', 'invoked the callback once');
this.clock.tick(asyncStream.delay);
assert.equal(calls, 'a', 'only invoked the callback once');
});
QUnit.test('executes callback in series', function(assert) {
const asyncStream = new AsyncStream();
let calls = '';
asyncStream.push(function() {
calls += 'a';
});
asyncStream.push(function() {
calls += 'b';
});
this.clock.tick(asyncStream.delay);
assert.equal(calls, 'a', 'invoked the first callback');
this.clock.tick(asyncStream.delay);
assert.equal(calls, 'ab', 'invoked the second');
});
QUnit.module('Incremental Decryption', {
beforeEach() {
this.clock = sinon.useFakeTimers();
},
afterEach() {
this.clock.restore();
}
});
QUnit.test('asynchronously decrypts a 4-word block', function(assert) {
const key = new Uint32Array([0, 0, 0, 0]);
const initVector = key;
// the string "howdy folks" encrypted
const encrypted = new Uint8Array([0xce, 0x90, 0x97, 0xd0,
0x08, 0x46, 0x4d, 0x18,
0x4f, 0xae, 0x01, 0x1c,
0x82, 0xa8, 0xf0, 0x67]);
let decrypted;
const decrypter = new Decrypter(
encrypted,
key,
initVector,
function(error, result) {
if (error) {
throw new Error(error);
}
decrypted = result;
}
);
assert.ok(!decrypted, 'asynchronously decrypts');
this.clock.tick(decrypter.asyncStream_.delay * 2);
assert.ok(decrypted, 'completed decryption');
assert.deepEqual(
'howdy folks',
stringFromBytes(decrypted),
'decrypts and unpads the result'
);
});
QUnit.test('breaks up input greater than the step value', function(assert) {
const encrypted = new Int32Array(Decrypter.STEP + 4);
let done = false;
const decrypter = new Decrypter(
encrypted,
new Uint32Array(4),
new Uint32Array(4),
function() {
done = true;
}
);
this.clock.tick(decrypter.asyncStream_.delay * 2);
assert.ok(!done, 'not finished after two ticks');
this.clock.tick(decrypter.asyncStream_.delay);
assert.ok(done, 'finished after the last chunk is decrypted');
});