# Languages

Video.js includes localization support to present text in a language other than the default English where appropriate.

For an up-to-date list of the languages Video.js supports, see the [languages folder (`lang`)][lang-supported].
Some translations may be less complete than others - see the [translations needed doc][translations-needed] for a table of strings that are missing from the translations available.  Contributions are welcome to update those that are incomplete.

## Table of Contents

* [Using Video.js Languages](#using-videojs-languages)
* [Contributing to Video.js Translations](#contributing-to-videojs-translations)
  * [JSON Format](#json-format)
  * [File Naming](#file-naming)
  * [Updating an Existing Translation](#updating-an-existing-translation)
  * [Writing a New Translation](#writing-a-new-translation)
  * [Adding Languages via the API](#adding-languages-via-the-api)
  * [Per-Player Translations](#per-player-translations)
  * [Setting Player Language](#setting-player-language)
  * [Determining Player Language](#determining-player-language)
    * [Internal Language Selection](#internal-language-selection)
* [References](#references)

## Using Video.js Languages

Video.js ships with multiple translations (in `dist/lang/`) in JavaScript files.
Add the lang script for each language you need to support.
Each of these files can be included in a web page to provide support for that language in _all_ Video.js players:

```html
<script src="//example.com/path/to/video.min.js"></script>
<script src="//example.com/path/to/lang/es.js"></script>
```

## Contributing to Video.js Translations

We welcome new translations and improvements to existing ones! Please see the [contributing document](../../CONTRIBUTING.md) to get started contributing to Video.js and continue reading for specifics on how to contribute to translations of Video.js.

### JSON Format

Video.js uses a JSON object to describe a language, where the keys are English and the values are the target language. For example, a Spanish translation might look like this:

```JSON
{
  "Play": "Reproducción",
  "Pause": "Pausa",
  "Current Time": "Tiempo reproducido",
  "Duration": "Duración total",
  "Remaining Time": "Tiempo restante",
}
```

### File Naming

Translations are found in the `lang/` directory.

Each file's name should be the [standard language code][lang-codes] that is most appropriate, with a `.json` extension. For example, "es.json" for Spanish or "zh-CN.json" for simplified Chinese.

### Updating an Existing Translation

If there is a [missing translation](/docs/translations-needed.md), mistake, or room for improvement in an existing translation, don't hesitate to open a pull request!

1. Edit the relevant JSON file and make the necessary changes.
1. Verify the language compiles by running the language specific build `npm run build:lang` or the full build `npm run build`.
1. Verify the translation appears properly in the player UI.
1. Run `npm run docs:lang` to update the [missing translation document](/docs/translations-needed.md).
1. Commit and open a pull request on GitHub.

### Writing a New Translation

The process for writing an entirely new translation is virtually identical to the process for [updating an existing translation](#updating-an-existing-translation) except that the new translation JSON file needs to be created.

The template for new language files is the English file ([lang/en.json][lang-en]). This file is always up-to-date with strings that need translations.

The first step to writing a new translation is to copy the English file:

```sh
cp lang/en.json lang/${NEW_LANG_CODE}.json
```

Otherwise, the process is the same as [updating an existing translation](#updating-an-existing-translation).

### Adding Languages via the API

In addition to the stand-alone scripts provided by Video.js, the API supports manual definition of new languages via the `addLanguage` method. It takes two arguments: the [standard language code][lang-codes] and a [language definition object](#json-format).

```js
videojs.addLanguage('es', {
  Play: 'Reproducción',
  Pause: 'Pausa',
  'Current Time': 'Tiempo reproducido',
  'Duration': 'Duración total',
  'Remaining Time': 'Tiempo restante',
  ...
});
```

`addLanguage()` will overwrite existing translations if the object includes strings previously translated. However text that has already been localised will not be updated after generation.

### Per-Player Translations

In addition to providing languages to Video.js itself, individual `Player` instances can be provided custom language support via [the `languages` option](/docs/guides/options.md#languages):

```js
// Provide a custom definition of Spanish to this player.
videojs('my-player', {
  languages: {
    es: {
      Play: 'Reproducir'
    }
  }
});
```

### Setting Player Language

The language used by a player instance may be set via [the `language` option](/docs/guides/options.md#language):

```js
// Set the language to Spanish for this player.
videojs('my-player', {
  language: 'es'
});
```

The `language` method of the player _can_ be used to set the language after instantiation with `language('es')`. However, this is generally not useful as it does not update text that is already in place.

### Determining Player Language

The player language is set to one of the following in descending priority:

* The language [specified in options](#setting-default-player-language)
* The language specified by a `lang` attribute on the player element.
* The language specified by the closest parent element with a `lang` attribute, up to and including the `<html>` element.
* The browser language preference; the first language if more than one is configured
* English

#### Internal Language Selection

* Language codes are considered case-insensitively (e.g. `en-US` == `en-us`).
* If there is no match for a language code with a subcode (e.g. `en-us`), a match for the primary code (e.g. `en`) is used if available.

## References

For information on translation/localization in plugins, see [the plugins guide](/docs/guides/plugins.md).

Standard languages codes [are defined by the IANA][lang-codes].

For all existing/supported languages, please see the [languages folder (`lang/`)][lang-supported] folder located in the project root.

[lang-en]: /lang/en.json

[lang-supported]: /lang

[lang-codes]: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry

[translations-needed]: https://github.com/videojs/video.js/blob/master/docs/translations-needed.md


keep keep keep keep keep replace replace keep keep keep

QUnit.test('clicking should disable non-selected tracks of the relevant kind(s)', function(assert) {

/* eslint-env qunit */
import TextTrackMenuItem from '../../../../src/js/control-bar/text-track-controls/text-track-menu-item.js';
import TestHelpers from '../../test-helpers.js';

QUnit.module('TextTrackMenuItem', {
  beforeEach(assert) {
    this.player = TestHelpers.makePlayer();
  },
  afterEach(assert) {
    this.player.dispose();
  }
});

QUnit.test('clicking should enable the selected track', function(assert) {
  assert.expect(2);

  const foo = this.player.addTextTrack('captions', 'foo', 'en');

  const fooItem = new TextTrackMenuItem(this.player, {
    track: foo
  });

  assert.strictEqual(foo.mode, 'disabled', 'track "foo" begins "disabled"');

  fooItem.trigger('click');

  assert.strictEqual(foo.mode, 'showing', 'clicking set track "foo" to "showing"');

  fooItem.dispose();
});

QUnit.test('clicking should disable non-selected tracks of the same kind', function(assert) {
  assert.expect(9);

  const foo = this.player.addTextTrack('captions', 'foo', 'en');
  const bar = this.player.addTextTrack('captions', 'bar', 'es');
  const bop = this.player.addTextTrack('metadata', 'bop');

  bop.mode = 'hidden';

  const fooItem = new TextTrackMenuItem(this.player, {
    track: foo
  });

  const barItem = new TextTrackMenuItem(this.player, {
    track: bar
  });

  assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" begins "disabled"');
  assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" begins "disabled"');
  assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is "hidden"');

  barItem.trigger('click');

  assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" is still "disabled"');
  assert.strictEqual(bar.mode, 'showing', 'captions track "bar" is now "showing"');
  assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is still "hidden"');

  fooItem.trigger('click');

  assert.strictEqual(foo.mode, 'showing', 'captions track "foo" is now "showing"');
  assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" is now "disabled"');
  assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is still "hidden"');

  fooItem.dispose();
  barItem.dispose();
});


import AudioTrackList from './audio-track-list';
import VideoTrackList from './video-track-list';
import TextTrackList from './text-track-list';
import HtmlTrackElementList from './html-track-element-list';

import TextTrack from './text-track';
import AudioTrack from './audio-track';
import VideoTrack from './video-track';
import HTMLTrackElement from './html-track-element';

import mergeOptions from '../utils/merge-options';

/*
 * This file contains all track properties that are used in
 * player.js, tech.js, html5.js and possibly other techs in the future.
 */

const NORMAL = {
  audio: {
    ListClass: AudioTrackList,
    TrackClass: AudioTrack,
    capitalName: 'Audio'
  },
  video: {
    ListClass: VideoTrackList,
    TrackClass: VideoTrack,
    capitalName: 'Video'
  },
  text: {
    ListClass: TextTrackList,
    TrackClass: TextTrack,
    capitalName: 'Text'
  }
};

Object.keys(NORMAL).forEach(function(type) {
  NORMAL[type].getterName = `${type}Tracks`;
  NORMAL[type].privateName = `${type}Tracks_`;
});

const REMOTE = {
  remoteText: {
    ListClass: TextTrackList,
    TrackClass: TextTrack,
    capitalName: 'RemoteText',
    getterName: 'remoteTextTracks',
    privateName: 'remoteTextTracks_'
  },
  remoteTextEl: {
    ListClass: HtmlTrackElementList,
    TrackClass: HTMLTrackElement,
    capitalName: 'RemoteTextTrackEls',
    getterName: 'remoteTextTrackEls',
    privateName: 'remoteTextTrackEls_'
  }
};

const ALL = mergeOptions(NORMAL, REMOTE);

REMOTE.names = Object.keys(REMOTE);
NORMAL.names = Object.keys(NORMAL);
ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);

export {
  NORMAL,
  REMOTE,
  ALL
};


/**
 * @file audio-track-list.js
 */
import TrackList from './track-list';

/**
 * Anywhere we call this function we diverge from the spec
 * as we only support one enabled audiotrack at a time
 *
 * @param {AudioTrackList} list
 *        list to work on
 *
 * @param {AudioTrack} track
 *        The track to skip
 *
 * @private
 */
const disableOthers = function(list, track) {
  for (let i = 0; i < list.length; i++) {
    if (!Object.keys(list[i]).length || track.id === list[i].id) {
      continue;
    }
    // another audio track is enabled, disable it
    list[i].enabled = false;
  }
};

/**
 * The current list of {@link AudioTrack} for a media file.
 *
 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
 * @extends TrackList
 */
class AudioTrackList extends TrackList {

  /**
   * Create an instance of this class.
   *
   * @param {AudioTrack[]} [tracks=[]]
   *        A list of `AudioTrack` to instantiate the list with.
   */
  constructor(tracks = []) {
    // make sure only 1 track is enabled
    // sorted from last index to first index
    for (let i = tracks.length - 1; i >= 0; i--) {
      if (tracks[i].enabled) {
        disableOthers(tracks, tracks[i]);
        break;
      }
    }

    super(tracks);
    this.changing_ = false;
  }

  /**
   * Add an {@link AudioTrack} to the `AudioTrackList`.
   *
   * @param {AudioTrack} track
   *        The AudioTrack to add to the list
   *
   * @fires TrackList#addtrack
   */
  addTrack(track) {
    if (track.enabled) {
      disableOthers(this, track);
    }

    super.addTrack(track);
    // native tracks don't have this
    if (!track.addEventListener) {
      return;
    }

    track.enabledChange_ = () => {
      // when we are disabling other tracks (since we don't support
      // more than one track at a time) we will set changing_
      // to true so that we don't trigger additional change events
      if (this.changing_) {
        return;
      }
      this.changing_ = true;
      disableOthers(this, track);
      this.changing_ = false;
      this.trigger('change');
    };

    /**
     * @listens AudioTrack#enabledchange
     * @fires TrackList#change
     */
    track.addEventListener('enabledchange', track.enabledChange_);
  }

  removeTrack(rtrack) {
    super.removeTrack(rtrack);

    if (rtrack.removeEventListener && rtrack.enabledChange_) {
      rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
      rtrack.enabledChange_ = null;
    }
  }
}

export default AudioTrackList;


# Collaborator Guide

## Table of Contents

* [Issues and Pull Requests](#issues-and-pull-requests)
  * [Labels](#labels)
* [Accepting changes](#accepting-changes)
  * [Involving the TSC](#involving-the-tsc)
* [Landing a PR](#landing-a-pr)
  * [Landing a PR manually](#landing-a-pr-manually)
    * [Landing a PR manually with several changes](#landing-a-pr-manually-with-several-changes)
    * [I just made a mistake](#i-just-made-a-mistake)
      * [I accidentally pushed a broken commit or incorrect commit to master](#i-accidentally-pushed-a-broken-commit-or-incorrect-commit-to-master)
      * [I lost changes](#i-lost-changes)
      * [I accidentally committed a broken change to master](#i-accidentally-committed-a-broken-change-to-master)
* [video.js releases](#videojs-releases)
  * [Getting dependencies](#getting-dependencies)
    * [npm access](#npm-access)
    * [GitHub personal access token](#github-personal-access-token)
  * [Deciding what type of version release](#deciding-what-type-of-version-release)
  * [Doing a release](#doing-a-release)
    * [Current Video.js](#current-videojs)
    * [Legacy Video.js (5)](#legacy-videojs-5)
      * [Edit git-semver-tags](#edit-git-semver-tags)
    * [And now for the release](#and-now-for-the-release)
  * [Deploy as a patch to the CDN](#deploy-as-a-patch-to-the-cdn)
  * [Announcement](#announcement)
* [Doc credit](#doc-credit)

## Issues and Pull Requests

Full courtesy should always be shown in video.js projects.

Collaborators may manage issues they feel qualified to handle, being mindful of our guidelines.

Any issue and PR can be closed if they are not relevant, when in doubt leave it open for more discussion. Issues can always be re-opened if new information is made available.

If issues or PRs are very short and don't contain much information, ask for more by linking to the [issue][issue template] or [PR][pr template] template. There is also a [response guide](https://github.com/videojs/video.js/wiki/New-Issue-Response-Guide) if you're unsure.

### Labels

There are labels that are useful to include on issues and PRs. A few of them are defined below:

| Label                    | Issue or PR  | Description                                                                |
| ------------------------ | ------------ | -------------------------------------------------------------------------- |
| confirmed                | Issue and PR | Issue: marks as reproducible. PR: marks as ready to be merged              |
| 5.x                      | PR           | Marks as a change to the 5.x branch only                                   |
| bug                      | Issue        | Marks as a confirmed bug                                                   |
| good first issue         | Issue        | Marks as a good bug or enhancement for first time contributors to Video.js |
| first-timers-only        | Issue        | Marks as a good bug or enhancement to be done by a newcomer to open source |
| minor, patch, major      | PR           | Marks PR with the expected semver classification of the change             |
| needs: LGTM              | PR           | Marks PR to be reviewed by a collaborator                                  |
| needs: more info         | Issue        | Marks as needing more information from the issue reporter                  |
| needs: reduced test case | Issue        | Marks as needing a reduced test case from the issue reporter               |

## Accepting changes

Any code change in video.js should be happening through Pull Requests on GitHub. This includes core committers.

Before a PR is merged, it must be reviewed by at least two core committers, at least one if it comes from a core committer.

Feel free to @-mention a particular core committer if you know they are experts in the area that is being changed.

If you are unsure about the modification and cannot take responsibility for it, defer to another core committer.

Before merging the change, it should be left open for other core committers to comment on. At least 24 hours during a weekday, and the 48 hours on a weekend. Trivial changes or bug fixes that have been reviewed by multiple committers may be merged without delay.

For non-breaking changes, if there is no disagreeming between the collaborators, the PR may be landed assuming it was reviewed. If there is still disagreement, it may need to be [escalated to the TSC](#involving-the-tsc).

Bug fixes require a test case that fails beforehand and succeeds after. All code changes should contain tests and pass on the CI.

### Involving the TSC

A change or issue can be elevated to the TSC by assing the `tsc-agent` label. This should be done in the following scenarios:

* There will be a major impact on the codebase or project
* The change is inherently controversial
* No agreement was reached between collaborators participating in the discussion

The TSC will be the final arbiter when required.

## Landing a PR

Landing a PR is fairly easy given that we can use the GitHub UI for it.

When using the big green button on GitHub, make sure the "squash and merge" is selected -- it should be the only allowed option. If a PR has two features in it and should be merged as two separate commits, either ask the contributor to break it up into two, or follow the [manual steps](#landing-a-pr-manually).

The commit message should follow our [conventional changelog conventions][conventions]. They are based on the angularjs changelog conventions. The changelog is then generated from these commit messages on release.

The first line of the commit message -- the header, which is the first text box on GitHub -- should be prefixed with a type and optional scope followed by a short description of the commit.
The type is required. Two common ones are `fix` and `feat` for bug fixes and new features. Scope is optional and can be anything.

The body should contain extra information, potentially copied from the original comment of the PR.

The footer should contain things like whether this is a breaking change or what issues were fixed by this PR.

Here's an example:

```commit
fix(html5): a regression with html5 tech

This is where you'd explain what the regression is.

Fixes #123
```

### Landing a PR manually

_Optional:_ ensure you're not in a weird rebase or merge state:

```sh
git am --abort
git rebase --abort
```

Checkout and update the master branch:

```sh
git checkout master
git remote update
git rebase upstream/master
```

Check out the PR:

```sh
git fetch upstream pull/{{PR Number}}/head:{{name of branch}}
git checkout -t {{name of branch}}
```

> For example:
>
> ```sh
> git fetch upstream pull/123/head:gkatsev-html5-fix
> git checkout -t gkatsev-html5-fix
> ```

_Optional:_ If necessary, rebase against master. If you have multiple features in the PR, [landing a PR manually with several changes](#landing-a-pr-manually-with-several-changes)

```sh
git rebase master
```

Fix up any issues that arise from the rebase, change back to the master branch and squash merge:

```sh
git checkout master
git merge --squash --no-commit gkatsev-html5-fix
```

The `--no-commit` tells git not to make a commit on your behalf. It does stage everything for you, so, you can instead it:

```sh
git diff --cached
```

Now get the author from the original commit:

```sh
git log -n 1 --pretty=short gkatsev-html5-fix
```

Which shows:

```txt
  commit 433c58224f5be34480c8e067ca6c5406ba1c1e9c
  Author: Gary Katsevman <git@gkatsev.com>

      Update TOC
```

Now you can commit the change the change with the author, following our commit guidelines

```sh
git commit --author "Gary Katsevman <git@gkatsev.com>"
```

Now that it's committed, push to master

```sh
git push upstream master
```

Congratulate yourself for a job well done and the contributor for having his change landed in master.

#### Landing a PR manually with several changes

Follow the same steps as before but when you rebase against master, you want to do an interactive rebase and then squash the changes into just a few commits.

```sh
git rebase -i master
```

This will give you an output like the following:

```txt
pick b4dc15d Update CONTRIBUTING.md with latest info
pick 8592149 Add Dev certificate of origin
pick 259dee6 Add grunt and doctoc npm scripts
pick f12af12 Add conventional-changelog-videojs link
pick ae4613a Update node's CONTRIBUTING.md url
pick 433c582 Update TOC

# Rebase f599ef4..433c582 onto f599ef4 (6 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
```

Replace `pick` to `fixup` or `edit` depending on how you want the output to look. You can also re-order the commits, if necessary.

> `fixup` will squash the commit it's infront of up into the commit above it
>
> `edit` will allow you to edit the commit message before continuing

```txt
edit b4dc15d Update CONTRIBUTING.md with latest info
fixup 8592149 Add Dev certificate of origin
fixup f12af12 Add conventional-changelog-videojs link
fixup ae4613a Update node's CONTRIBUTING.md url
fixup 433c582 Update TOC
edit 259dee6 Add grunt and doctoc npm scripts
```

When you get to the edit commits, git will give more information, but you'd want to run ammend the current commit while following our commit guidelines

```sh
git commit --amend
```

After going through and making the commits you want, you want to change back to master and then rebase the branch onto master so we get a clean history

```sh
git rebase gkatsev-html5-fix
```

This will put our two commits into master:

```txt
b4dc15d chore(contributing.md): Update CONTRIBUTING.md with latest info <Gary Katsevman>
259dee6 chore(package.json): Add grunt and doctoc npm scripts <Gary Katsevman>
9e20386 v5.12.6 <Gary Katsevman>
```

Now you're ready to push to master as in the normal instructions.

#### I just made a mistake

While `git` allows you to update the remote branch with a force push (`git push -f`). This is generally frowned upon since you're rewriting public history. However, if you just pushed the change and it's been less than 10 minutes since you've done with, you may force push to update the commit, assuming no one else has already pushed after you.

##### I accidentally pushed a broken commit or incorrect commit to master

Assuming no more than 10 minutes have passed, you may force-push to update or remove the commit. If someone else has already pushed to master or 10 minutes have passed, you should instead use the revert command (`git revert`) to revert the commit and then commit the proper change, or just fix it forward with a followup commit that fixes things.

##### I lost changes

Assuming that the changes were committed, even if you lost the commit in your current history does not mean that it is lost. In a lot of cases you can still recover it from the PR branch or if all else fails look at [git's reflog](https://git-scm.com/docs/git-reflog).

##### I accidentally committed a broken change to master

This is a great time to discover that something is broken. Because it hasn't been pushed to GitHub yet, it's very easy to reset the change as if nothing has happened and try again.

To do so, just reset the branch against master.

```sh
git reset --hard upstream/master
```

## video.js releases

Releasing video.js is partially automated through various scripts.
To do a release, you need a couple of things: npm access, GitHub personal access token.

Releases in video.js are done on npm and GitHub and eventually posted on the CDN.
These are the instructions for the npm/GitHub releases.

When we do a release, we release it as a `next` tag on npm first and then at least a week later, we promote this release to `latest` on npm.

### Getting dependencies

#### npm access

To see who currently has access run this:

```sh
npm owner ls video.js
```

If you are a core committer, you can request access to npm from one of the current owners.
Access is managed via an [npm organization][npm org] for [Video.js][vjs npm].

#### GitHub personal access token

This is used to make a GitHub release on videojs. You can get a token from the [personal access tokens](https://github.com/settings/tokens) page.

After generating one, make sure to keep it safe because GitHub will not show the token for you again. A good place to save it is Lastpass Secure Notes.

### Deciding what type of version release

Since we follow the [conventional changelog conventions][conventions],
all commits are prepended with a type, most commonly `feat` and `fix`.
If all the commits are fix or other types such as `test` or `chore`, then the release will be a `patch` release.
If there's even one `feat`, the release will be a `minor` release.
If any commit has a `BREAKING CHANGE` footer, then the release will be a `major` release.
Most common releases will be either `patch` or `minor`.

### Doing a release

It is also recommended you have a clean clone of Video.js for each release line you want to release.
That means having a folder for master/v6 and one for 5.x.
This is because 5.x and 6.x have different versions expecations for release process and have different dependencies.
Plus, during development you could end up with a dirty repo, so, it just usually easier if you have a clean release repo.

```sh
# for v6
git clone git@github.com:videojs/video.js.git videojs-6-release
# for v5
git clone git@github.com:videojs/video.js.git videojs-5-release
```

#### Current Video.js

Make sure go to the master branch and grab the latest updates.

```sh
git checkout master
git pull origin master
```

At this point, you should run `npm install` because dependencies may have changed.

Then, it's mostly a standard npm package release process with running `npm version`, followed by an `npm publish`.

```sh
npm version {major|minor|patch}
```

Depending on the commits that have been merged, you can choose from `major`, `minor`, or `patch` as the versioning values.
See [deciding what type of version release section](#deciding-what-type-of-version-release).

Optionally, you can run `git show` now to verify that the version update and CHANGELOG automation worked as expected.

Afterwards, you want to push the commit and the tag to the repo.
It's necessary to do this before running `npm publish` because our GitHub release automation
relies on the commit being available on GitHub.

```sh
git push --tags origin master
```

Finally, run `npm publish` with an appropriate tag. Don't forget to supply your token.

```sh
VJS_GITHUB_USER=gkatsev VJS_GITHUB_TOKEN=my-personal-access-token npm publish --tag next
```

After it's done, verify that the GitHub release has the correct changelog output.
This is to make sure that the CHANGELOG didn't get garbled and isn't missing pieces.

If the GitHub release did not work correctly, such as if the GitHub token was not provided,
you can run it manually:

```sh
VJS_GITHUB_USER=gkatsev VJS_GITHUB_TOKEN=123 node build/gh-release.js --prelease
```

#### Legacy Video.js (5)

Make sure to go to the 5.x branch and grab the latest updates.

```sh
git checkout 5.x
git pull origin 5.x
```

> _Note:_ you probably need to delete v6 tags due to the way that the our CHANGELOG lib works.
>
> You can run this to delete them:
>
> ```sh
> git tag | grep '^v6' | xargs git tag -d
> ```
>
> This will find all tags that start with `^v6` and delete them.

At this point, you should run `npm install` because dependencies may have changed.

Then, we have a script that automates most of the steps for publishing. It's a little trickier than publishing v6.

##### Edit git-semver-tags

You'll need to edit `git-semver-tags` to support our usage of tags that are not part of the branch.
In the file `node_modules/conventional-changelog-cli/node_modules/conventional-changelog/node_modules/conventional-changelog-core/node_modules/git-semver-tags/index.js`, edit the line that says sets the `cmd` to be:

```js
var cmd = 'git log --all --date-order --decorate --no-color';
```

#### And now for the release

After getting rid of the tags and getting the latest updates, you can just run the release script:

```sh
VJS_GITHUB_USER=gkatsev VJS_GITHUB_TOKEN=123 ./build/bin/release-next.sh
```

It will prompt you for a version change type, so, input `patch` or `minor` or `major`.
See [deciding what type of version release section](#deciding-what-type-of-version-release).

When it's done building everything, it'll show you the commit that's made via the default pager (i.e., less).
At this point you can verify that things look normal rather than, for example, missing all the CSS.

After exiting the pager, it'll make sure you want to continue with publishing.

It will automatically release it as a `next-5` tag on npm.

Then push the local changes up:

```sh
git push --tags origin 5.x
```

Also, you'll need to copy the CHANGELOG for this version and manually edit the GitHub release to include it.
The current release's CHANGELOG could be copied from the [raw CHANGELOG.md file][raw chg] (or locally from the markdown file)
and then pasted into the correct [GitHub release](https://github.com/videojs/video.js/releases).

### Deploy as a patch to the CDN

Follow the steps on the [CDN repo][cdn repo] for the CDN release process.
If it's a `next` or `next-5` release, only publish the patch version to the CDN.

When the version gets promoted to `latest` or `latest-5`, the corresponding `minor` or `latest` version should be published to the CDN.

### Announcement

An announcement should automatically make it's way to #announcements channel on [slack][], it uses IFTTT and might take a while.

You can also post it to twitter or ask someone (like @gkatsev) to post on your behalf.

If it's a large enough release, consider writing a blog post as well.

## Doc credit

This collaborator guide was heavily inspired by [node.js's guide](https://github.com/nodejs/node/blob/master/COLLABORATOR_GUIDE.md)

[issue template]: /.github/ISSUE_TEMPLATE.md

[pr template]: /.github/PULL_REQUEST_TEMPLATE.md

[conventions]: https://github.com/videojs/conventional-changelog-videojs/blob/master/convention.md

[vjs npm]: https://www.npmjs.com/org/videojs

[npm org]: https://docs.npmjs.com/misc/orgs

[slack]: http://slack.videojs.com

[cdn repo]: https://github.com/videojs/cdn

[raw chg]: https://raw.githubusercontent.com/videojs/video.js/5.x/CHANGELOG.md


const bop = this.player.addTextTrack('metadata', 'bop');

const bip = this.player.addTextTrack('subtitles', 'bip', 'fr');


import {VideoTrackKind} from './track-enums';
import Track from './track';
import merge from '../utils/merge-options';

/**
 * A representation of a single `VideoTrack`.
 *
 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
 * @extends Track
 */
class VideoTrack extends Track {

  /**
   * Create an instance of this class.
   *
   * @param {Object} [options={}]
   *        Object of option names and values
   *
   * @param {string} [options.kind='']
   *        A valid {@link VideoTrack~Kind}
   *
   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
   *        A unique id for this AudioTrack.
   *
   * @param {string} [options.label='']
   *        The menu label for this track.
   *
   * @param {string} [options.language='']
   *        A valid two character language code.
   *
   * @param {boolean} [options.selected]
   *        If this track is the one that is currently playing.
   */
  constructor(options = {}) {
    const settings = merge(options, {
      kind: VideoTrackKind[options.kind] || ''
    });

    super(settings);

    let selected = false;

    /**
     * @memberof VideoTrack
     * @member {boolean} selected
     *         If this `VideoTrack` is selected or not. When setting this will
     *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
     * @instance
     *
     * @fires VideoTrack#selectedchange
     */
    Object.defineProperty(this, 'selected', {
      get() {
        return selected;
      },
      set(newSelected) {
        // an invalid or unchanged value
        if (typeof newSelected !== 'boolean' || newSelected === selected) {
          return;
        }
        selected = newSelected;

        /**
         * An event that fires when selected changes on this track. This allows
         * the VideoTrackList that holds this track to act accordingly.
         *
         * > Note: This is not part of the spec! Native tracks will do
         *         this internally without an event.
         *
         * @event VideoTrack#selectedchange
         * @type {EventTarget~Event}
         */
        this.trigger('selectedchange');
      }
    });

    // if the user sets this track to selected then
    // set selected to that true value otherwise
    // we keep it false
    if (settings.selected) {
      this.selected = settings.selected;
    }
  }
}

export default VideoTrack;


<!DOCTYPE html>

  <meta charset="utf-8" />
  <title>Index of video.js examples</title>

Index of video.js examples

Video.js HTML5 video player simple example

Elephants Dream video with text descriptions, chapters & captions example


const fooItem = new TextTrackMenuItem(this.player, {

const barItem = new TextTrackMenuItem(this.player, {

track: foo,
kinds: ['captions', 'subtitles']

/**
 * @file off-text-track-menu-item.js
 */
import TextTrackMenuItem from './text-track-menu-item.js';
import Component from '../../component.js';

/**
 * A special menu item for turning of a specific type of text track
 *
 * @extends TextTrackMenuItem
 */
class OffTextTrackMenuItem extends TextTrackMenuItem {

  /**
   * Creates an instance of this class.
   *
   * @param {Player} player
   *        The `Player` that this class should be attached to.
   *
   * @param {Object} [options]
   *        The key/value store of player options.
   */
  constructor(player, options) {
    // Create pseudo track info
    // Requires options['kind']
    options.track = {
      player,
      kind: options.kind,
      kinds: options.kinds,
      default: false,
      mode: 'disabled'
    };

    if (!options.kinds) {
      options.kinds = [options.kind];
    }

    if (options.label) {
      options.track.label = options.label;
    } else {
      options.track.label = options.kinds.join(' and ') + ' off';
    }

    // MenuItem is selectable
    options.selectable = true;
    // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
    options.multiSelectable = false;

    super(player, options);
  }

  /**
   * Handle text track change
   *
   * @param {EventTarget~Event} event
   *        The event that caused this function to run
   */
  handleTracksChange(event) {
    const tracks = this.player().textTracks();
    let shouldBeSelected = true;

    for (let i = 0, l = tracks.length; i < l; i++) {
      const track = tracks[i];

      if ((this.options_.kinds.indexOf(track.kind) > -1) && track.mode === 'showing') {
        shouldBeSelected = false;
        break;
      }
    }

    // Prevent redundant selected() calls because they may cause
    // screen readers to read the appended control text unnecessarily
    if (shouldBeSelected !== this.isSelected_) {
      this.selected(shouldBeSelected);
    }
  }

  handleSelectedLanguageChange(event) {
    const tracks = this.player().textTracks();
    let allHidden = true;

    for (let i = 0, l = tracks.length; i < l; i++) {
      const track = tracks[i];

      if ((['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1) && track.mode === 'showing') {
        allHidden = false;
        break;
      }
    }

    if (allHidden) {
      this.player_.cache_.selectedLanguage = {
        enabled: false
      };
    }
  }

}

Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
export default OffTextTrackMenuItem;


/* eslint-env qunit */
import videojs from '../../src/js/video.js';
import document from 'global/document';
import sinon from 'sinon';
import log from '../../src/js/utils/log.js';

QUnit.module('video.js:hooks ', {
  beforeEach() {
    videojs.hooks_ = {};
  },
  afterEach() {
    videojs.hooks_ = {};
  }
});

QUnit.test('should be able to add a hook', function(assert) {
  videojs.hook('foo', function() {});
  assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook type');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');

  videojs.hook('bar', function() {});
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hook types');
  assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');

  videojs.hook('bar', function() {});
  assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');

  videojs.hook('foo', [function() {}, function() {}, function() {}]);
  assert.equal(videojs.hooks_.foo.length, 4, 'should have 4 foo hooks');
  assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks');
});

QUnit.test('should be able to remove a hook', function(assert) {
  const noop = function() {};

  videojs.hook('foo', noop);
  assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook types');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');

  videojs.hook('bar', noop);
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
  assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');

  const fooRetval = videojs.removeHook('foo', noop);

  assert.equal(fooRetval, true, 'should return true');
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
  assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hook');
  assert.equal(videojs.hooks_.bar.length, 1, 'should have 0 bar hook');

  const barRetval = videojs.removeHook('bar', noop);

  assert.equal(barRetval, true, 'should return true');
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
  assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hook');
  assert.equal(videojs.hooks_.bar.length, 0, 'should have 0 bar hook');

  const errRetval = videojs.removeHook('bar', noop);

  assert.equal(errRetval, false, 'should return false');
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
  assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hook');
  assert.equal(videojs.hooks_.bar.length, 0, 'should have 0 bar hook');
});

QUnit.test('should be able get all hooks for a type', function(assert) {
  const noop = function() {};

  videojs.hook('foo', noop);
  assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook types');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');

  videojs.hook('bar', noop);
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
  assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');

  const fooHooks = videojs.hooks('foo');
  const barHooks = videojs.hooks('bar');

  assert.deepEqual(videojs.hooks_.foo, fooHooks, 'should return the exact foo list from videojs.hooks_');
  assert.deepEqual(videojs.hooks_.bar, barHooks, 'should return the exact bar list from videojs.hooks_');
});

QUnit.test('should be get all hooks for a type and add at the same time', function(assert) {
  const noop = function() {};

  videojs.hook('foo', noop);
  assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook types');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');

  videojs.hook('bar', noop);
  assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
  assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
  assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');

  const fooHooks = videojs.hooks('foo', noop);
  const barHooks = videojs.hooks('bar', noop);

  assert.deepEqual(videojs.hooks_.foo.length, 2, 'foo should have two noop hooks');
  assert.deepEqual(videojs.hooks_.bar.length, 2, 'bar should have two noop hooks');
  assert.deepEqual(videojs.hooks_.foo, fooHooks, 'should return the exact foo list from videojs.hooks_');
  assert.deepEqual(videojs.hooks_.bar, barHooks, 'should return the exact bar list from videojs.hooks_');
});

QUnit.test('should be able to add a hook that runs once', function(assert) {
  const spies = [
    sinon.spy(),
    sinon.spy(),
    sinon.spy()
  ];

  videojs.hookOnce('foo', spies);

  assert.equal(videojs.hooks_.foo.length, 3, 'should have 3 foo hooks');

  videojs.hooks('foo').forEach(fn => fn());

  spies.forEach((spy, i) => {
    assert.ok(spy.calledOnce, `spy #${i + 1} was called`);
  });

  assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hooks');
});

QUnit.test('hooks registered using hookOnce should return the original callback return value', function(assert) {
  let result;

  videojs.hookOnce('foo', () => 'ok');
  videojs.hooks('foo').forEach(fn => {
    result = fn();
  });

  assert.equal(result, 'ok', 'the hookOnce callback returned "ok"');
});

QUnit.test('should trigger beforesetup and setup during videojs setup', function(assert) {
  const vjsOptions = {techOrder: ['techFaker']};
  let setupCalled = false;
  let beforeSetupCalled = false;
  const beforeSetup = function(video, options) {
    beforeSetupCalled = true;
    assert.equal(setupCalled, false, 'setup should be called after beforesetup');
    assert.deepEqual(options, vjsOptions, 'options should be the same');
    assert.equal(video.id, 'test_vid_id', 'video id should be correct');

    return options;
  };
  const setup = function(player) {
    setupCalled = true;

    assert.equal(beforeSetupCalled, true, 'beforesetup should have been called already');
    assert.ok(player, 'created player from tag');
    assert.ok(player.id() === 'test_vid_id');
    assert.ok(
      videojs.getPlayers().test_vid_id === player,
      'added player to global reference'
    );
  };

  const fixture = document.getElementById('qunit-fixture');

  fixture.innerHTML += '<video id="test_vid_id"><source type="video/mp4"></video>';

  const vid = document.getElementById('test_vid_id');

  videojs.hook('beforesetup', beforeSetup);
  videojs.hook('setup', setup);

  const player = videojs(vid, vjsOptions);

  assert.ok(player.options_, 'returning null in beforesetup does not lose options');
  assert.equal(beforeSetupCalled, true, 'beforeSetup was called');
  assert.equal(setupCalled, true, 'setup was called');

  player.dispose();
});

QUnit.test('beforesetup returns dont break videojs options', function(assert) {
  const oldLogError = log.error;
  const vjsOptions = {techOrder: ['techFaker']};
  const fixture = document.getElementById('qunit-fixture');

  log.error = function(err) {
    assert.equal(err, 'please return an object in beforesetup hooks', 'we have the correct error');
  };

  fixture.innerHTML += '<video id="test_vid_id"><source type="video/mp4"></video>';

  const vid = document.getElementById('test_vid_id');

  videojs.hook('beforesetup', function() {
    return null;
  });
  videojs.hook('beforesetup', function() {
    return '';
  });
  videojs.hook('beforesetup', function() {
    return [];
  });

  const player = videojs(vid, vjsOptions);

  assert.ok(player.options_, 'beforesetup should not destory options');
  assert.equal(player.options_.techOrder, vjsOptions.techOrder, 'options set by user should exist');

  log.error = oldLogError;

  player.dispose();
});

QUnit.test('beforesetup options override videojs options', function(assert) {
  const vjsOptions = {techOrder: ['techFaker'], autoplay: false};
  const fixture = document.getElementById('qunit-fixture');

  fixture.innerHTML += '<video id="test_vid_id"><source type="video/mp4"></video>';

  const vid = document.getElementById('test_vid_id');

  videojs.hook('beforesetup', function(tag, options) {
    assert.equal(options.autoplay, false, 'false was passed to us');
    return {autoplay: true};
  });

  const player = videojs(vid, vjsOptions);

  assert.ok(player.options_, 'beforesetup should not destory options');
  assert.equal(player.options_.techOrder, vjsOptions.techOrder, 'options set by user should exist');
  assert.equal(player.options_.autoplay, true, 'autoplay should be set to true now');

  player.dispose();
});


# Text Tracks

Text tracks are a feature of HTML5 for displaying time-triggered text to the end-user. Video.js offers a cross-browser implementation of text tracks.

## Table of Contents

* [A Note on "Remote" Text Tracks](#a-note-on-remote-text-tracks)
* [Creating the Text File](#creating-the-text-file)
* [Adding Text Tracks to Video.js](#adding-text-tracks-to-videojs)
  * [track Attributes](#track-attributes)
    * [kind](#kind)
    * [label](#label)
    * [default](#default)
    * [srclang](#srclang)
  * [Text Tracks from Another Domain](#text-tracks-from-another-domain)
* [Working with Text Tracks](#working-with-text-tracks)
  * [Showing Tracks Programmatically](#showing-tracks-programmatically)
  * [Listen for a Cue Becoming Active](#listen-for-a-cue-becoming-active)
* [Emulated Text Tracks](#emulated-text-tracks)
  * [Text Track Settings](#text-track-settings)
* [Text Track Precedence](#text-track-precedence)
* [API](#api)
  * [Remote Text Tracks](#remote-text-tracks)
  * [Text Tracks](#text-tracks)

## A Note on "Remote" Text Tracks

Video.js refers to so-called "remote" text tracks. This is a convenient term for tracks that have an associated `<track>` element rather than those that do not.

Either can be created programmatically, but _only remote text tracks can be removed from a player._ For that reason, we recommend _only_ using remote text tracks.

## Creating the Text File

Timed text requires a text file in [WebVTT](https://dev.w3.org/html5/webvtt/) format. This format defines a list of "cues" that have a start time, an end time, and text to display. [Microsoft has a builder](https://dev.modern.ie/testdrive/demos/captionmaker/) that can help you get started on the file.

> **Note:** When creating captions, there are additional [caption formatting techniques](https://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML#style) to make captions more meaningful, like brackets around sound effects (e.g. `[ birds chirping ]`).
>
> For a more in depth style guide for captioning, see the [Captioning Key](https://www.dcmp.org/captioningkey/), but keep in mind not all features are supported by WebVTT or (more likely) the Video.js WebVTT implementation.

## Adding Text Tracks to Video.js

Once you have your WebVTT files created, you can add them to your `video` element using the `track` tag. Similar to `source` elements, `track` elements should be added as children of the `video` element:

```html
<video
  class="video-js"
  controls
  preload="auto"
  width="640"
  height="264"
  data-setup='{}'>
  <source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
  <source src="//vjs.zencdn.net/v/oceans.webm" type="video/webm">
  <track kind="captions" src="//example.com/path/to/captions.vtt" srclang="en" label="English" default>
</video>
```

Video.js will automatically read `track` elements from the `video` element. Tracks (remote and non-remote) can also be added programmatically.

### `track` Attributes

#### `kind`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#text-track-kind)

One of the track types supported by Video.js:

* `"subtitles"` (default): Translations of the dialogue in the video for when audio is available but not understood. Subtitles are shown over the video.
* `"captions"`: Transcription of the dialogue, sound effects, musical cues, and other audio information for viewer who are deaf/hard of hearing, or the video is muted. Captions are also shown over the video.
* `"chapters"`: Chapter titles that are used to create navigation within the video. Typically, these are in the form of a list of chapters that the viewer can use to navigate the video.
* `"descriptions"`: Text descriptions of the action in the content for when the video portion isn't available or because the viewer is blind or not using a screen. Descriptions are read by a screen reader or turned into a separate audio track.
* `"metadata"`: Tracks that have data meant for JavaScript to parse and do something with. These aren't shown to the user.

#### `label`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#text-track-label)

Short descriptive text for the track that will used in the user interface. For example, in a menu for selecting a captions language.

#### `default`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-track-default)

The boolean `default` attribute can be used to indicate that a track's mode should start as `"showing"`. Otherwise, the viewer would need to select their language from a captions or subtitles menu.

> **Note:** For chapters, `default` is required if you want the chapters menu to show.

#### `srclang`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-track-srclang)

The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of the text track, e.g. `"en"` for English or `"es"` for Spanish.

For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang) folder located in the Video.js root and refer to the [languages guide](/docs/guides/languages.md) for more information on languages in Video.js.

### Text Tracks from Another Domain

Because Video.js loads the text track file via JavaScript, the [same-origin policy](https://en.wikipedia.org/wiki/Same_origin_policy) applies. If you'd like to have a player served from one domain, but the text track served from another, you'll need to [enable CORS](https://enable-cors.org/) on the server that is serving your text tracks.

In addition to enabling CORS, you will need to add the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) to the video element itself. This attribute has two possible values `"anonymous"` and `"use-credentials"`. Most users will want to use `"anonymous"` with cross-origin tracks:

```html
<video class="video-js" crossorigin="anonymous">
  <source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
  <track src="//example.com/oceans.vtt" kind="captions" srclang="en" label="English">
</video>
```

One thing to be aware of is that the video files themselves will _also_ need CORS headers. This is because some browsers apply the `crossorigin` attribute to the video source itself and not just the tracks. This is considered a [security concern by the spec](https://html.spec.whatwg.org/multipage/embedded-content.html#security-and-privacy-considerations).

## Working with Text Tracks

### Showing Tracks Programmatically

Certain use cases call for turning captions on and off programmatically rather than forcing the user to do so themselves. This can be easily achieved by modifying the `mode` property of the text tracks.

The `mode` can be one of three values `"disabled"`, `"hidden"`, and `"showing"`. When a text track's `mode` is `"disabled"`, the track does not show on screen as the video is playing.

When the `mode` is set to `"showing"`, the track is visible to the viewer and updates while the video is playing.

```js
// Get all text tracks for the current player.
var tracks = player.textTracks();

for (var i = 0; i < tracks.length; i++) {
  var track = tracks[i];

  // Find the English captions track and mark it as "showing".
  if (track.kind === 'captions' && track.language === 'en') {
    track.mode = 'showing';
  }
}
```

### Listen for a Cue Becoming Active

One of the supported values for `mode` is `"hidden"`. This `mode` means that the track will update as the video is playing, but it won't be visible to the viewer. This is most useful for tracks where `kind="metadata"`.

A common use case for metadata text tracks is to use them to trigger behaviors when their cues become active. For this purpose, tracks emit a `"cuechange"` event.

```js
// Get all text tracks for the current player.
var tracks = player.textTracks();
var metadataTrack;

for (var i = 0; i < tracks.length; i++) {
  var track = tracks[i];

  // Find the metadata track that's labeled "ads".
  if (track.kind === 'metadata' && track.label === 'ads') {
    track.mode = 'hidden';

    // Store it for usage outside of the loop.
    metadataTrack = track;
  }
}

// Add a listener for the "cuechange" event and start ad playback.
metadataTrack.addEventListener('cuechange', function() {
  player.ads.startLinearAdMode();
});
```

## Emulated Text Tracks

By default, Video.js will use native text tracks and fall back to emulated text tracks if the native functionality is broken, incomplete, or non-existent. The Flash tech will always use the emulated text track functionality.

The Video.js API and TextTrack objects were modeled after the W3C specification. Video.js uses [Mozilla's vtt.js](https://github.com/mozilla/vtt.js) library to parse and display emulated text tracks.

To disable native text track functionality and force Video.js to use emulated text tracks always, the `nativeTextTracks` option can be passed to a tech:

```js
// Create a player, passing `nativeTextTracks: false` to the HTML5 tech.
var player = videojs('myvideo', {
  html5: {
    nativeTextTracks: false
  }
});
```

### Text Track Settings

When using emulated text tracks, captions will have an additional item in the menu called "Caption Settings". This allows the user to alter how captions are styled on screen.

This feature can be disabled by turning off the `TextTrackSettings` component and hiding the menu item.

```js
var player = videojs('myvideo', {
  // Make the text track settings dialog not initialize.
  textTrackSettings: false
});
```

```css
/* Hide the captions settings item from the captions menu. */
.vjs-texttrack-settings {
  display: none;
}
```

## Text Track Precedence

In general, `"descriptions"` tracks are of lower precedence than `"captions"` and `"subtitles"`. What this mean for developers using Video.js?

* If you are using the `default` attribute, Video.js will choose the first track that is marked as `default` and turn it on. If there are multiple tracks marked `default`, it will turn on the first `"captions"` or `"subtitles"` track _before_ any `"descriptions"` tracks.
  * This only applied to the emulated text track support, native text tracks behavior will change depending on the browser.
* If a track is selected from the menu, Video.js will turn off all the other tracks of the same kind. While this suggests Video.js supports both `"subtitles"` and `"captions"` being turned on simultaneously, this is currently not the case; Video.js only supports one track being displayed at a time.
  * This means that for emulated text tracks, Video.js will display the first enabled `"subtitles"` or `"captions"` track.
  * When native text tracks are supported, other tracks of the same kind will still be disabled, but it is possible that multiple text tracks are shown.
  * If a `"descriptions"` track is selected and subsequently a `"subtitles"` or `"captions"` track is selected, the `"descriptions"` track is disabled and its menu button is also disabled.
* When enabling a track programmatically, Video.js performs minimal enforcement.
  * For emulated text tracks, Video.js chooses the first track that's `"showing"` - again choosing `"subtitles"` or `"captions"` over `"descriptions"`.
  * For native text tracks, this behavior depends on the browser. Some browsers will allow multiple text tracks, but others will disable all other tracks when a new one is selected.

## API

For more complete information, refer to the [Video.js API docs](https://docs.videojs.com/).

### Remote Text Tracks

As mentioned [above](#a-note-on-remote-text-tracks), remote text tracks represent the recommended API offered by Video.js as they can be removed.

* `Player#remoteTextTracks()`

* `Player#remoteTextTrackEls()`

* `Player#addRemoteTextTrack(Object options)`

  Available options are the same as the [available `track` attributes](#track-attributes). And `language` is a supported option as an alias for the `srclang` attribute - either works here.

  **Note**: If you need a callback, instead of a callback you could use the technique below:

  ```js
  const trackEl = player.addRemoteTextTrack({src: 'en.vtt'}, false);
  trackEl.addEventListener('load', function() {
    // your callback goes here
  });
  ```

* `Player#removeRemoteTextTrack(HTMLTrackElement|TextTrack)`

### Text Tracks

It is generally recommended that you use _remote_ text tracks rather than these purely programmatic text tracks for the majority of use-cases.

* `Player#textTracks()`

* `Player#addTextTrack(String kind, [String label [, String language]])`

  > **Note:** Non-remote text tracks are intended for _purely programmatic usage_ of tracks and have the important limitation that they _cannot be removed once created_.
  >
  > The standard `addTextTrack` does **not** have a corresponding `removeTextTrack` method; so, we actually discourage the use of this method!

* `TextTrackList()`

* `TextTrack()`


/* eslint-disable no-var */
/* eslint-env qunit */
var videojs = require('../../');
var videojsCore = require('../../core');

QUnit.module('Webpack Require');
QUnit.test('videojs should be requirable and bundled via webpack', function(assert) {
  assert.ok(videojs, 'videojs is required properly');
  assert.ok(videojsCore, 'videojs core is required properly');
});


assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" begins "disabled"');

assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" begins "disabled"');

keep keep keep replace keep keep keep keep

const bipItem = new TextTrackMenuItem(this.player, {


/**
 * @file log.js
 * @module log
 */
import CreateLogger from './create-logger.js';

const log = CreateLogger('VIDEOJS');
const createLogger = log.createLogger;

export default log;
export { createLogger };


Τρέχων χρόνος

Συνολικός χρόνος

Υπολοιπόμενος χρόνος

Φόρτωση επιτυχής

Πλήρης οθόνη

Έξοδος από πλήρη οθόνη

Kατάργηση σίγασης

Ρυθμός αναπαραγωγής

απόκρυψη υπότιτλων

απόκρυψη λεζάντων

Κλείσιμο παραθύρου

απόκρυψη περιγραφών

Ακυρώσατε την αναπαραγωγή

Ένα σφάλμα δικτύου προκάλεσε την αποτυχία μεταφόρτωσης του αρχείου προς αναπαραγωγή.

Το αρχείο προς αναπαραγωγή δεν ήταν δυνατό να φορτωθεί είτε γιατί υπήρξε σφάλμα στον διακομιστή ή το δίκτυο, είτε γιατί ο τύπος του αρχείου δεν υποστηρίζεται.

Η αναπαραγωγή ακυρώθηκε είτε λόγω κατεστραμμένου αρχείου, είτε γιατί το αρχείο απαιτεί λειτουργίες που δεν υποστηρίζονται από το πρόγραμμα περιήγησης που χρησιμοποιείτε.

Δεν βρέθηκε συμβατή πηγή αναπαραγωγής για το συγκεκριμένο αρχείο.

Το αρχείο προς αναπαραγωγή είναι κρυπτογραφημένo και δεν υπάρχουν τα απαραίτητα κλειδιά αποκρυπτογράφησης.

Αναπαραγωγή βίντεο

Aναδυόμενο παράθυρο

Το παρών είναι ένα αναδυόμενο παράθυρο

Αυτό το παράθυρο μπορεί να εξαφανιστεί πατώντας το πλήκτρο Escape ή πατώντας το κουμπί κλεισίματος.

, εμφανίζει τις ρυθμίσεις για τις λεζάντες

, εμφανίζει τις ρυθμίσεις για τους υπότιτλους

, εμφανίζει τις ρυθμίσεις για τις περιγραφές

, επιλεγμένο


assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is "hidden"');

assert.strictEqual(bip.mode, 'disabled', 'subtitles track "bip" begins "disabled"');


.video-js .vjs-captions-button .vjs-icon-placeholder {
  @extend .vjs-icon-captions;
}


<!DOCTYPE html>

  <meta charset="utf-8" />
  <title>Video.js Sandbox</title>
  <link href="../dist/video-js.css" rel="stylesheet" type="text/css">

You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started run `npm start` and open the liveui.html

npm start

open http://localhost:9999/sandbox/liveui.html

  <video-js id="vid1" controls preload="auto" width="640"  height="264">
    <source src="https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8" type="application/x-mpegURL">

To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video

  </video-js>


# Video Tracks

> **Note:** While video tracks [are a standard][spec-videotrack], there are no compatible implementations at this time. This is an experimental technology!

Video tracks are a feature of HTML5 video for providing alternate video tracks to the user, so they can change the type of video they want to watch. Video.js offers a cross-browser implementation of video tracks.

## Table of Contents

* [Caveats](#caveats)
* [Working with Video Tracks](#working-with-video-tracks)
  * [Add a Video Track to the Player](#add-a-video-track-to-the-player)
  * [Listen for a Video Track Becoming Enabled](#listen-for-a-video-track-becoming-enabled)
  * [Removing an Video Track from the Player](#removing-an-video-track-from-the-player)
* [API](#api)
  * [videojs.VideoTrack](#videojsvideotrack)
    * [id](#id)
    * [kind](#kind)
    * [label](#label)
    * [language](#language)
    * [selected](#selected)

## Caveats

* It is not possible to add video tracks through HTML like you can with text tracks. They must be added programmatically.
* Video.js only stores track representations. Switching video tracks for playback is _not handled by Video.js_ and must be handled elsewhere.

## Working with Video Tracks

### Add a Video Track to the Player

```js
// Create a player.
var player = videojs('my-player');

// Create a track object.
var track = new videojs.VideoTrack({
  id: 'my-alternate-video-track',
  kind: 'commentary',
  label: 'Director\'s Commentary',
  language: 'en'
});

// Add the track to the player's video track list.
player.videoTracks().addTrack(track);
```

### Listen for a Video Track Becoming Enabled

When a track is enabled or disabled on an `VideoTrackList`, a `change` event will be fired. You can listen for that event and do something with it.

> NOTE: The initial `VideoTrack` selection (usually the main track that is selected) should not fire a `change` event.

```js
// Get the current player's VideoTrackList object.
var videoTrackList = player.videoTracks();

// Listen to the "change" event.
videoTrackList.addEventListener('change', function() {

  // Log the currently enabled VideoTrack label.
  for (var i = 0; i < videoTrackList.length; i++) {
    var track = videoTrackList[i];

    if (track.enabled) {
      videojs.log(track.label);
      return;
    }
  }
});
```

### Removing an Video Track from the Player

Assuming a player already exists and has an video track that you want to remove, you might do something like the following:

```js
// Get the track we created in an earlier example.
var track = player.videoTracks().getTrackById('my-alternate-video-track');

// Remove it from the video track list.
player.videoTracks().removeTrack(track);
```

## API

For more complete information, refer to the [Video.js API docs](https://docs.videojs.com/), specifically:

* `Player#videoTracks`
* `VideoTrackList`
* `VideoTrack`

### `videojs.VideoTrack`

This class is based on [the `VideoTrack` standard][spec-videotrack] and can be used to create new video track objects.

Each property below is available as an option to the `VideoTrack` constructor.

#### `id`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-id)

A unique identifier for this track. Video.js will generate one if not given.

#### `kind`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind)

Video.js supports standard `kind` values for `VideoTracks`:

* `"alternative"`: A possible alternative to the main track.
* `"captions"`: The main video track with burned in captions
* `"main"`: The main video track.
* `"sign"`: The main video track with added sign language overlay.
* `"subtitles"`: The main video track with burned in subtitles.
* `"commentary"`: The main video track with burned in commentary.
* `""` (default): No explicit kind, or the kind given by the track's metadata is not recognized by the user agent.

#### `label`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-label)

The label for the track that will be shown to the user. For example, in a menu that lists the different captions available as alternate video tracks.

#### `language`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-language)

The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of the video track, e.g. `"en"` for English or `"es"` for Spanish.

For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang) folder located in the Video.js root and refer to the [languages guide](/docs/guides/languages.md) for more information on languages in Video.js.

#### `selected`

> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-selected)

Whether or not this track should be playing. Only one video track may be selected at a time.

[spec-videotrack]: https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack


assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" is still "disabled"');

assert.strictEqual(bar.mode, 'showing', 'captions track "bar" is now "showing"');

assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is still "hidden"');

assert.strictEqual(foo.mode, 'showing', 'captions track "foo" is now "showing"');

keep keep keep keep add keep keep keep keep keep

assert.strictEqual(bip.mode, 'disabled', 'subtitles track "bip" is still "disabled"');


/**
 * @file button.js
 */
import ClickableComponent from './clickable-component.js';
import Component from './component';
import log from './utils/log.js';
import {assign} from './utils/obj';
import keycode from 'keycode';

/**
 * Base class for all buttons.
 *
 * @extends ClickableComponent
 */
class Button extends ClickableComponent {

  /**
   * Create the `Button`s DOM element.
   *
   * @param {string} [tag="button"]
   *        The element's node type. This argument is IGNORED: no matter what
   *        is passed, it will always create a `button` element.
   *
   * @param {Object} [props={}]
   *        An object of properties that should be set on the element.
   *
   * @param {Object} [attributes={}]
   *        An object of attributes that should be set on the element.
   *
   * @return {Element}
   *         The element that gets created.
   */
  createEl(tag, props = {}, attributes = {}) {
    tag = 'button';

    props = assign({
      innerHTML: '',
      className: this.buildCSSClass()
    }, props);

    // Add attributes for button element
    attributes = assign({

      // Necessary since the default button type is "submit"
      type: 'button'
    }, attributes);

    const el = Component.prototype.createEl.call(this, tag, props, attributes);

    this.createControlTextEl(el);

    return el;
  }

  /**
   * Add a child `Component` inside of this `Button`.
   *
   * @param {string|Component} child
   *        The name or instance of a child to add.
   *
   * @param {Object} [options={}]
   *        The key/value store of options that will get passed to children of
   *        the child.
   *
   * @return {Component}
   *         The `Component` that gets added as a child. When using a string the
   *         `Component` will get created by this process.
   *
   * @deprecated since version 5
   */
  addChild(child, options = {}) {
    const className = this.constructor.name;

    log.warn(`Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`);

    // Avoid the error message generated by ClickableComponent's addChild method
    return Component.prototype.addChild.call(this, child, options);
  }

  /**
   * Enable the `Button` element so that it can be activated or clicked. Use this with
   * {@link Button#disable}.
   */
  enable() {
    super.enable();
    this.el_.removeAttribute('disabled');
  }

  /**
   * Disable the `Button` element so that it cannot be activated or clicked. Use this with
   * {@link Button#enable}.
   */
  disable() {
    super.disable();
    this.el_.setAttribute('disabled', 'disabled');
  }

  /**
   * This gets called when a `Button` has focus and `keydown` is triggered via a key
   * press.
   *
   * @param {EventTarget~Event} event
   *        The event that caused this function to get called.
   *
   * @listens keydown
   */
  handleKeyPress(event) {
    // Ignore Space or Enter key operation, which is handled by the browser for a button.
    if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {

      // Pass keypress handling up for unsupported keys
      super.handleKeyPress(event);
    }
  }
}

Component.registerComponent('Button', Button);
export default Button;


/**
 * @file format-time.js
 * @module format-time
 */

/**
 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
 * seconds) will force a number of leading zeros to cover the length of the
 * guide.
 *
 * @private
 * @param  {number} seconds
 *         Number of seconds to be turned into a string
 *
 * @param  {number} guide
 *         Number (in seconds) to model the string after
 *
 * @return {string}
 *         Time formatted as H:MM:SS or M:SS
 */
const defaultImplementation = function(seconds, guide) {
  seconds = seconds < 0 ? 0 : seconds;
  let s = Math.floor(seconds % 60);
  let m = Math.floor(seconds / 60 % 60);
  let h = Math.floor(seconds / 3600);
  const gm = Math.floor(guide / 60 % 60);
  const gh = Math.floor(guide / 3600);

  // handle invalid times
  if (isNaN(seconds) || seconds === Infinity) {
    // '-' is false for all relational operators (e.g. <, >=) so this setting
    // will add the minimum number of fields specified by the guide
    h = m = s = '-';
  }

  // Check if we need to show hours
  h = (h > 0 || gh > 0) ? h + ':' : '';

  // If hours are showing, we may need to add a leading zero.
  // Always show at least one digit of minutes.
  m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';

  // Check if leading zero is need for seconds
  s = (s < 10) ? '0' + s : s;

  return h + m + s;
};

// Internal pointer to the current implementation.
let implementation = defaultImplementation;

/**
 * Replaces the default formatTime implementation with a custom implementation.
 *
 * @param {Function} customImplementation
 *        A function which will be used in place of the default formatTime
 *        implementation. Will receive the current time in seconds and the
 *        guide (in seconds) as arguments.
 */
export function setFormatTime(customImplementation) {
  implementation = customImplementation;
}

/**
 * Resets formatTime to the default implementation.
 */
export function resetFormatTime() {
  implementation = defaultImplementation;
}

/**
 * Delegates to either the default time formatting function or a custom
 * function supplied via `setFormatTime`.
 *
 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
 * guide (in seconds) will force a number of leading zeros to cover the
 * length of the guide.
 *
 * @static
 * @example  formatTime(125, 600) === "02:05"
 * @param    {number} seconds
 *           Number of seconds to be turned into a string
 *
 * @param    {number} guide
 *           Number (in seconds) to model the string after
 *
 * @return   {string}
 *           Time formatted as H:MM:SS or M:SS
 */
function formatTime(seconds, guide = seconds) {
  return implementation(seconds, guide);
}

export default formatTime;


assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" is now "disabled"');

assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is still "hidden"');

assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" is now "disabled"');

assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" is still "disabled"');

assert.strictEqual(bip.mode, 'showing', 'subtitles track "bip" is now "showing"');


/**
 * @file text-track.js
 */
import TextTrackCueList from './text-track-cue-list';
import * as Fn from '../utils/fn.js';
import {TextTrackKind, TextTrackMode} from './track-enums';
import log from '../utils/log.js';
import window from 'global/window';
import Track from './track.js';
import { isCrossOrigin } from '../utils/url.js';
import XHR from 'xhr';
import merge from '../utils/merge-options';

/**
 * Takes a webvtt file contents and parses it into cues
 *
 * @param {string} srcContent
 *        webVTT file contents
 *
 * @param {TextTrack} track
 *        TextTrack to add cues to. Cues come from the srcContent.
 *
 * @private
 */
const parseCues = function(srcContent, track) {
  const parser = new window.WebVTT.Parser(
    window,
    window.vttjs,
    window.WebVTT.StringDecoder()
  );
  const errors = [];

  parser.oncue = function(cue) {
    track.addCue(cue);
  };

  parser.onparsingerror = function(error) {
    errors.push(error);
  };

  parser.onflush = function() {
    track.trigger({
      type: 'loadeddata',
      target: track
    });
  };

  parser.parse(srcContent);
  if (errors.length > 0) {
    if (window.console && window.console.groupCollapsed) {
      window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
    }
    errors.forEach((error) => log.error(error));
    if (window.console && window.console.groupEnd) {
      window.console.groupEnd();
    }
  }

  parser.flush();
};

/**
 * Load a `TextTrack` from a specified url.
 *
 * @param {string} src
 *        Url to load track from.
 *
 * @param {TextTrack} track
 *        Track to add cues to. Comes from the content at the end of `url`.
 *
 * @private
 */
const loadTrack = function(src, track) {
  const opts = {
    uri: src
  };
  const crossOrigin = isCrossOrigin(src);

  if (crossOrigin) {
    opts.cors = crossOrigin;
  }

  XHR(opts, Fn.bind(this, function(err, response, responseBody) {
    if (err) {
      return log.error(err, response);
    }

    track.loaded_ = true;

    // Make sure that vttjs has loaded, otherwise, wait till it finished loading
    // NOTE: this is only used for the alt/video.novtt.js build
    if (typeof window.WebVTT !== 'function') {
      if (track.tech_) {
        // to prevent use before define eslint error, we define loadHandler
        // as a let here
        let loadHandler;
        const errorHandler = () => {
          log.error(`vttjs failed to load, stopping trying to process ${track.src}`);
          track.tech_.off('vttjsloaded', loadHandler);
        };

        loadHandler = () => {
          track.tech_.off('vttjserror', errorHandler);
          return parseCues(responseBody, track);
        };

        track.tech_.one('vttjsloaded', loadHandler);
        track.tech_.one('vttjserror', errorHandler);
      }
    } else {
      parseCues(responseBody, track);
    }

  }));
};

/**
 * A representation of a single `TextTrack`.
 *
 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
 * @extends Track
 */
class TextTrack extends Track {

  /**
   * Create an instance of this class.
   *
   * @param {Object} options={}
   *        Object of option names and values
   *
   * @param {Tech} options.tech
   *        A reference to the tech that owns this TextTrack.
   *
   * @param {TextTrack~Kind} [options.kind='subtitles']
   *        A valid text track kind.
   *
   * @param {TextTrack~Mode} [options.mode='disabled']
   *        A valid text track mode.
   *
   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
   *        A unique id for this TextTrack.
   *
   * @param {string} [options.label='']
   *        The menu label for this track.
   *
   * @param {string} [options.language='']
   *        A valid two character language code.
   *
   * @param {string} [options.srclang='']
   *        A valid two character language code. An alternative, but deprioritized
   *        version of `options.language`
   *
   * @param {string} [options.src]
   *        A url to TextTrack cues.
   *
   * @param {boolean} [options.default]
   *        If this track should default to on or off.
   */
  constructor(options = {}) {
    if (!options.tech) {
      throw new Error('A tech was not provided.');
    }

    const settings = merge(options, {
      kind: TextTrackKind[options.kind] || 'subtitles',
      language: options.language || options.srclang || ''
    });
    let mode = TextTrackMode[settings.mode] || 'disabled';
    const default_ = settings.default;

    if (settings.kind === 'metadata' || settings.kind === 'chapters') {
      mode = 'hidden';
    }
    super(settings);

    this.tech_ = settings.tech;

    this.cues_ = [];
    this.activeCues_ = [];

    const cues = new TextTrackCueList(this.cues_);
    const activeCues = new TextTrackCueList(this.activeCues_);
    let changed = false;
    const timeupdateHandler = Fn.bind(this, function() {

      // Accessing this.activeCues for the side-effects of updating itself
      // due to its nature as a getter function. Do not remove or cues will
      // stop updating!
      // Use the setter to prevent deletion from uglify (pure_getters rule)
      this.activeCues = this.activeCues;
      if (changed) {
        this.trigger('cuechange');
        changed = false;
      }
    });

    if (mode !== 'disabled') {
      this.tech_.ready(() => {
        this.tech_.on('timeupdate', timeupdateHandler);
      }, true);
    }

    Object.defineProperties(this, {
      /**
       * @memberof TextTrack
       * @member {boolean} default
       *         If this track was set to be on or off by default. Cannot be changed after
       *         creation.
       * @instance
       *
       * @readonly
       */
      default: {
        get() {
          return default_;
        },
        set() {}
      },

      /**
       * @memberof TextTrack
       * @member {string} mode
       *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
       *         not be set if setting to an invalid mode.
       * @instance
       *
       * @fires TextTrack#modechange
       */
      mode: {
        get() {
          return mode;
        },
        set(newMode) {
          if (!TextTrackMode[newMode]) {
            return;
          }
          mode = newMode;
          if (mode !== 'disabled') {
            this.tech_.ready(() => {
              this.tech_.on('timeupdate', timeupdateHandler);
            }, true);
          } else {
            this.tech_.off('timeupdate', timeupdateHandler);
          }
          /**
           * An event that fires when mode changes on this track. This allows
           * the TextTrackList that holds this track to act accordingly.
           *
           * > Note: This is not part of the spec!
           *
           * @event TextTrack#modechange
           * @type {EventTarget~Event}
           */
          this.trigger('modechange');

        }
      },

      /**
       * @memberof TextTrack
       * @member {TextTrackCueList} cues
       *         The text track cue list for this TextTrack.
       * @instance
       */
      cues: {
        get() {
          if (!this.loaded_) {
            return null;
          }

          return cues;
        },
        set() {}
      },

      /**
       * @memberof TextTrack
       * @member {TextTrackCueList} activeCues
       *         The list text track cues that are currently active for this TextTrack.
       * @instance
       */
      activeCues: {
        get() {
          if (!this.loaded_) {
            return null;
          }

          // nothing to do
          if (this.cues.length === 0) {
            return activeCues;
          }

          const ct = this.tech_.currentTime();
          const active = [];

          for (let i = 0, l = this.cues.length; i < l; i++) {
            const cue = this.cues[i];

            if (cue.startTime <= ct && cue.endTime >= ct) {
              active.push(cue);
            } else if (cue.startTime === cue.endTime &&
                       cue.startTime <= ct &&
                       cue.startTime + 0.5 >= ct) {
              active.push(cue);
            }
          }

          changed = false;

          if (active.length !== this.activeCues_.length) {
            changed = true;
          } else {
            for (let i = 0; i < active.length; i++) {
              if (this.activeCues_.indexOf(active[i]) === -1) {
                changed = true;
              }
            }
          }

          this.activeCues_ = active;
          activeCues.setCues_(this.activeCues_);

          return activeCues;
        },

        // /!\ Keep this setter empty (see the timeupdate handler above)
        set() {}
      }
    });

    if (settings.src) {
      this.src = settings.src;
      loadTrack(settings.src, this);
    } else {
      this.loaded_ = true;
    }
  }

  /**
   * Add a cue to the internal list of cues.
   *
   * @param {TextTrack~Cue} cue
   *        The cue to add to our internal list
   */
  addCue(originalCue) {
    let cue = originalCue;

    if (window.vttjs && !(originalCue instanceof window.vttjs.VTTCue)) {
      cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);

      for (const prop in originalCue) {
        if (!(prop in cue)) {
          cue[prop] = originalCue[prop];
        }
      }

      // make sure that `id` is copied over
      cue.id = originalCue.id;
      cue.originalCue_ = originalCue;
    }

    const tracks = this.tech_.textTracks();

    for (let i = 0; i < tracks.length; i++) {
      if (tracks[i] !== this) {
        tracks[i].removeCue(cue);
      }
    }

    this.cues_.push(cue);
    this.cues.setCues_(this.cues_);
  }

  /**
   * Remove a cue from our internal list
   *
   * @param {TextTrack~Cue} removeCue
   *        The cue to remove from our internal list
   */
  removeCue(removeCue) {
    let i = this.cues_.length;

    while (i--) {
      const cue = this.cues_[i];

      if (cue === removeCue || (cue.originalCue_ && cue.originalCue_ === removeCue)) {
        this.cues_.splice(i, 1);
        this.cues.setCues_(this.cues_);
        break;
      }
    }
  }
}

/**
 * cuechange - One or more cues in the track have become active or stopped being active.
 */
TextTrack.prototype.allowedEvents_ = {
  cuechange: 'cuechange'
};

export default TextTrack;


/**
 * @file menu-button.js
 */
import Button from '../button.js';
import Component from '../component.js';
import Menu from './menu.js';
import * as Dom from '../utils/dom.js';
import * as Fn from '../utils/fn.js';
import * as Events from '../utils/events.js';
import toTitleCase from '../utils/to-title-case.js';
import { IS_IOS } from '../utils/browser.js';
import document from 'global/document';
import keycode from 'keycode';

/**
 * A `MenuButton` class for any popup {@link Menu}.
 *
 * @extends Component
 */
class MenuButton extends Component {

  /**
   * Creates an instance of this class.
   *
   * @param {Player} player
   *        The `Player` that this class should be attached to.
   *
   * @param {Object} [options={}]
   *        The key/value store of player options.
   */
  constructor(player, options = {}) {
    super(player, options);

    this.menuButton_ = new Button(player, options);

    this.menuButton_.controlText(this.controlText_);
    this.menuButton_.el_.setAttribute('aria-haspopup', 'true');

    // Add buildCSSClass values to the button, not the wrapper
    const buttonClass = Button.prototype.buildCSSClass();

    this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass;
    this.menuButton_.removeClass('vjs-control');

    this.addChild(this.menuButton_);

    this.update();

    this.enabled_ = true;

    this.on(this.menuButton_, 'tap', this.handleClick);
    this.on(this.menuButton_, 'click', this.handleClick);
    this.on(this.menuButton_, 'focus', this.handleFocus);
    this.on(this.menuButton_, 'blur', this.handleBlur);
    this.on(this.menuButton_, 'mouseenter', () => {
      this.menu.show();
    });
    this.on('keydown', this.handleSubmenuKeyPress);
  }

  /**
   * Update the menu based on the current state of its items.
   */
  update() {
    const menu = this.createMenu();

    if (this.menu) {
      this.menu.dispose();
      this.removeChild(this.menu);
    }

    this.menu = menu;
    this.addChild(menu);

    /**
     * Track the state of the menu button
     *
     * @type {Boolean}
     * @private
     */
    this.buttonPressed_ = false;
    this.menuButton_.el_.setAttribute('aria-expanded', 'false');

    if (this.items && this.items.length <= this.hideThreshold_) {
      this.hide();
    } else {
      this.show();
    }
  }

  /**
   * Create the menu and add all items to it.
   *
   * @return {Menu}
   *         The constructed menu
   */
  createMenu() {
    const menu = new Menu(this.player_, { menuButton: this });

    /**
     * Hide the menu if the number of items is less than or equal to this threshold. This defaults
     * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
     * it here because every time we run `createMenu` we need to reset the value.
     *
     * @protected
     * @type {Number}
     */
    this.hideThreshold_ = 0;

    // Add a title list item to the top
    if (this.options_.title) {
      const titleEl = Dom.createEl('li', {
        className: 'vjs-menu-title',
        innerHTML: toTitleCase(this.options_.title),
        tabIndex: -1
      });

      this.hideThreshold_ += 1;

      const titleComponent = new Component(this.player_, {el: titleEl});

      menu.addItem(titleComponent);
    }

    this.items = this.createItems();

    if (this.items) {
      // Add menu items to the menu
      for (let i = 0; i < this.items.length; i++) {
        menu.addItem(this.items[i]);
      }
    }

    return menu;
  }

  /**
   * Create the list of menu items. Specific to each subclass.
   *
   * @abstract
   */
  createItems() {}

  /**
   * Create the `MenuButtons`s DOM element.
   *
   * @return {Element}
   *         The element that gets created.
   */
  createEl() {
    return super.createEl('div', {
      className: this.buildWrapperCSSClass()
    }, {
    });
  }

  /**
   * Allow sub components to stack CSS class names for the wrapper element
   *
   * @return {string}
   *         The constructed wrapper DOM `className`
   */
  buildWrapperCSSClass() {
    let menuButtonClass = 'vjs-menu-button';

    // If the inline option is passed, we want to use different styles altogether.
    if (this.options_.inline === true) {
      menuButtonClass += '-inline';
    } else {
      menuButtonClass += '-popup';
    }

    // TODO: Fix the CSS so that this isn't necessary
    const buttonClass = Button.prototype.buildCSSClass();

    return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}`;
  }

  /**
   * Builds the default DOM `className`.
   *
   * @return {string}
   *         The DOM `className` for this object.
   */
  buildCSSClass() {
    let menuButtonClass = 'vjs-menu-button';

    // If the inline option is passed, we want to use different styles altogether.
    if (this.options_.inline === true) {
      menuButtonClass += '-inline';
    } else {
      menuButtonClass += '-popup';
    }

    return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}`;
  }

  /**
   * Get or set the localized control text that will be used for accessibility.
   *
   * > NOTE: This will come from the internal `menuButton_` element.
   *
   * @param {string} [text]
   *        Control text for element.
   *
   * @param {Element} [el=this.menuButton_.el()]
   *        Element to set the title on.
   *
   * @return {string}
   *         - The control text when getting
   */
  controlText(text, el = this.menuButton_.el()) {
    return this.menuButton_.controlText(text, el);
  }

  /**
   * Handle a click on a `MenuButton`.
   * See {@link ClickableComponent#handleClick} for instances where this is called.
   *
   * @param {EventTarget~Event} event
   *        The `keydown`, `tap`, or `click` event that caused this function to be
   *        called.
   *
   * @listens tap
   * @listens click
   */
  handleClick(event) {
    if (this.buttonPressed_) {
      this.unpressButton();
    } else {
      this.pressButton();
    }
  }

  /**
   * Set the focus to the actual button, not to this element
   */
  focus() {
    this.menuButton_.focus();
  }

  /**
   * Remove the focus from the actual button, not this element
   */
  blur() {
    this.menuButton_.blur();
  }

  /**
   * This gets called when a `MenuButton` gains focus via a `focus` event.
   * Turns on listening for `keydown` events. When they happen it
   * calls `this.handleKeyPress`.
   *
   * @param {EventTarget~Event} event
   *        The `focus` event that caused this function to be called.
   *
   * @listens focus
   */
  handleFocus() {
    Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress));
  }

  /**
   * Called when a `MenuButton` loses focus. Turns off the listener for
   * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
   *
   * @param {EventTarget~Event} event
   *        The `blur` event that caused this function to be called.
   *
   * @listens blur
   */
  handleBlur() {
    Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
  }

  /**
   * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
   * {@link ClickableComponent#handleKeyPress} for instances where this is called.
   *
   * @param {EventTarget~Event} event
   *        The `keydown` event that caused this function to be called.
   *
   * @listens keydown
   */
  handleKeyPress(event) {
    // Escape or Tab unpress the 'button'
    if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
      if (this.buttonPressed_) {
        this.unpressButton();
      }
      // Don't preventDefault for Tab key - we still want to lose focus
      if (!keycode.isEventKey(event, 'Tab')) {
        event.preventDefault();
        // Set focus back to the menu button's button
        this.menuButton_.focus();
      }
    // Up Arrow or Down Arrow also 'press' the button to open the menu
    } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
      if (!this.buttonPressed_) {
        event.preventDefault();
        this.pressButton();
      }
    } else {
      // NOTE: This is a special case where we don't pass unhandled
      //  keypress events up to the Component handler, because it is
      //  just entending the keypress handling of the actual `Button`
      //  in the `MenuButton` which already passes unused keys up.
    }
  }

  /**
   * Handle a `keydown` event on a sub-menu. The listener for this is added in
   * the constructor.
   *
   * @param {EventTarget~Event} event
   *        Key press event
   *
   * @listens keydown
   */
  handleSubmenuKeyPress(event) {
    // Escape or Tab unpress the 'button'
    if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
      if (this.buttonPressed_) {
        this.unpressButton();
      }
      // Don't preventDefault for Tab key - we still want to lose focus
      if (!keycode.isEventKey(event, 'Tab')) {
        event.preventDefault();
        // Set focus back to the menu button's button
        this.menuButton_.focus();
      }
    } else {
      // NOTE: This is a special case where we don't pass unhandled
      //  keypress events up to the Component handler, because it is
      //  just entending the keypress handling of the `MenuItem`
      //  in the `Menu` which already passes unused keys up.
    }
  }

  /**
   * Put the current `MenuButton` into a pressed state.
   */
  pressButton() {
    if (this.enabled_) {
      this.buttonPressed_ = true;
      this.menu.show();
      this.menu.lockShowing();
      this.menuButton_.el_.setAttribute('aria-expanded', 'true');

      // set the focus into the submenu, except on iOS where it is resulting in
      // undesired scrolling behavior when the player is in an iframe
      if (IS_IOS && Dom.isInFrame()) {
        // Return early so that the menu isn't focused
        return;
      }

      this.menu.focus();
    }
  }

  /**
   * Take the current `MenuButton` out of a pressed state.
   */
  unpressButton() {
    if (this.enabled_) {
      this.buttonPressed_ = false;
      this.menu.unlockShowing();
      this.menu.hide();
      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
    }
  }

  /**
   * Disable the `MenuButton`. Don't allow it to be clicked.
   */
  disable() {
    this.unpressButton();

    this.enabled_ = false;
    this.addClass('vjs-disabled');

    this.menuButton_.disable();
  }

  /**
   * Enable the `MenuButton`. Allow it to be clicked.
   */
  enable() {
    this.enabled_ = true;
    this.removeClass('vjs-disabled');

    this.menuButton_.enable();
  }
}

Component.registerComponent('MenuButton', MenuButton);
export default MenuButton;


.video-js button {
  background: none;
  border: none;
  color: inherit;
  display: inline-block;

  font-size: inherit; // IE in general. WTF.
  line-height: inherit;
  text-transform: none;
  text-decoration: none;
  transition: none;

  -webkit-appearance: none;
  -moz-appearance:    none;
  appearance:         none;
}

.vjs-control .vjs-button {
  width: 100%;
  height: 100%;
}


/* eslint-env qunit */
import TextTrackMenuItem from '../../../../src/js/control-bar/text-track-controls/text-track-menu-item.js';
import TestHelpers from '../../test-helpers.js';

QUnit.module('TextTrackMenuItem', {
  beforeEach(assert) {
    this.player = TestHelpers.makePlayer();
  },
  afterEach(assert) {
    this.player.dispose();
  }
});

QUnit.test('clicking should enable the selected track', function(assert) {
  assert.expect(2);

  const foo = this.player.addTextTrack('captions', 'foo', 'en');

  const fooItem = new TextTrackMenuItem(this.player, {
    track: foo
  });

  assert.strictEqual(foo.mode, 'disabled', 'track "foo" begins "disabled"');

  fooItem.trigger('click');

  assert.strictEqual(foo.mode, 'showing', 'clicking set track "foo" to "showing"');

  fooItem.dispose();
});

QUnit.test('clicking should disable non-selected tracks of the same kind', function(assert) {
  assert.expect(9);

  const foo = this.player.addTextTrack('captions', 'foo', 'en');
  const bar = this.player.addTextTrack('captions', 'bar', 'es');
  const bop = this.player.addTextTrack('metadata', 'bop');

  bop.mode = 'hidden';

  const fooItem = new TextTrackMenuItem(this.player, {
    track: foo
  });

  const barItem = new TextTrackMenuItem(this.player, {
    track: bar
  });

  assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" begins "disabled"');
  assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" begins "disabled"');
  assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is "hidden"');

  barItem.trigger('click');

  assert.strictEqual(foo.mode, 'disabled', 'captions track "foo" is still "disabled"');
  assert.strictEqual(bar.mode, 'showing', 'captions track "bar" is now "showing"');
  assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is still "hidden"');

  fooItem.trigger('click');

  assert.strictEqual(foo.mode, 'showing', 'captions track "foo" is now "showing"');
  assert.strictEqual(bar.mode, 'disabled', 'captions track "bar" is now "disabled"');
  assert.strictEqual(bop.mode, 'hidden', 'metadata track "bop" is still "hidden"');

  fooItem.dispose();
  barItem.dispose();
});


پخش کننده صوتی

پخش کننده ویدیو

زمان باقیمانده

غیر تمام‌صفحه

بدون زیرنویس

بدون زیرتوضیح

شما پخش را قطع کردید.

خطای شبکه باعث عدم بارگیری بخشی از رسانه شد.

.رسانه قابل بارگیری نیست.   علت آن ممکن است خطا در اتصال یا عدم پشتیبانی از فرمت باشد

پخش  رسانه به علت اشکال در آن یا عدم پشتیبانی مرورگر شما قطع شد.

هیچ منبع سازگاری، برای این رسانه پیدا نشد.

این رسانه رمزنگاری شده است و ما کلید رمزگشایی آن را نداریم.

بستن پنجره مودال

این پنجره مودال

این پنجره با دکمه اسکیپ با دکمه بستن قابل بسته شدن میباشد.

, تنظیمات زیرتوضیح را باز میکند

, تنظیمات زیرنویس را باز میکند

, تنظیمات توصیفات را باز میکند

, انتخاب شده

تنظیمات زیرتوضیح

تنظیمات زیرنویس

تنظیمات توصیفات

متن

Context: زمینه

سنس-سریف متناسب

سنس-سریف هم اندازه

سریف هم اندازه

حروف کوچک به بزرگ

بازیابی همه تنظیمات به حالت اولیه

پنجره تنظیمات عناوین

ابتدای پنجره محاوره‌ای. دکمه اسکیپ پنجره را لغو میکند و میبندد.

انتهای پنجره محاوره‌ای.