Add support for multiple audio tracks
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -1955,6 +1955,7 @@
|
|||||||
"version": "0.10.1",
|
"version": "0.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn-promise/-/cross-spawn-promise-0.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn-promise/-/cross-spawn-promise-0.10.1.tgz",
|
||||||
"integrity": "sha1-25y0xQxgtyoVvgSbeBIs44LYexA=",
|
"integrity": "sha1-25y0xQxgtyoVvgSbeBIs44LYexA=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cross-spawn": "^5.1.0"
|
"cross-spawn": "^5.1.0"
|
||||||
},
|
},
|
||||||
@@ -1963,6 +1964,7 @@
|
|||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lru-cache": "^4.0.1",
|
"lru-cache": "^4.0.1",
|
||||||
"shebang-command": "^1.2.0",
|
"shebang-command": "^1.2.0",
|
||||||
@@ -2497,6 +2499,7 @@
|
|||||||
"version": "0.7.3",
|
"version": "0.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.7.3.tgz",
|
||||||
"integrity": "sha512-l4chYFTWr6uWODKYUXeC+Z4tqGa3b8e+Y2WUBf3F7Ruv6yYzZ+Ccic65oXreeotx09B7sUx1KTuwXRsRJHKlMw==",
|
"integrity": "sha512-l4chYFTWr6uWODKYUXeC+Z4tqGa3b8e+Y2WUBf3F7Ruv6yYzZ+Ccic65oXreeotx09B7sUx1KTuwXRsRJHKlMw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asar": "^2.0.1",
|
"asar": "^2.0.1",
|
||||||
"cross-spawn-promise": "^0.10.1",
|
"cross-spawn-promise": "^0.10.1",
|
||||||
@@ -2513,6 +2516,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
@@ -2521,6 +2525,7 @@
|
|||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -2570,7 +2575,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-2.0.0.tgz",
|
||||||
"integrity": "sha512-kf7+/t8XIp1I6LIV9v6K38rBHzmY6bUr3TunJZKdlIKQ7j6wyjjpgbpxSBcg3S7pgzq1kkgCYZvpr8CsLFVivw==",
|
"integrity": "sha512-kf7+/t8XIp1I6LIV9v6K38rBHzmY6bUr3TunJZKdlIKQ7j6wyjjpgbpxSBcg3S7pgzq1kkgCYZvpr8CsLFVivw==",
|
||||||
"dev": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"electron-installer-common": "^0.7.1",
|
"electron-installer-common": "^0.7.1",
|
||||||
@@ -2584,7 +2589,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
"dev": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
@@ -2593,7 +2598,7 @@
|
|||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
"dev": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -9062,6 +9067,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.0.2.tgz",
|
||||||
"integrity": "sha512-zl71nFWjPKW2KXs+73gEk8RmqvtAeXPxhWDkTUoa3MSMkjq3I+9OeknjF178MQoMYsdqL730hfzvNfEkePxq9Q==",
|
"integrity": "sha512-zl71nFWjPKW2KXs+73gEk8RmqvtAeXPxhWDkTUoa3MSMkjq3I+9OeknjF178MQoMYsdqL730hfzvNfEkePxq9Q==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tmp": "0.1.0"
|
"tmp": "0.1.0"
|
||||||
}
|
}
|
||||||
@@ -10045,7 +10051,8 @@
|
|||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"wordwrap": {
|
"wordwrap": {
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ function init (state, options) {
|
|||||||
useContentSize: true, // Specify web page size without OS chrome
|
useContentSize: true, // Specify web page size without OS chrome
|
||||||
width: initialBounds.width,
|
width: initialBounds.width,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true
|
nodeIntegration: true,
|
||||||
|
experimentalFeatures: true
|
||||||
},
|
},
|
||||||
x: initialBounds.x,
|
x: initialBounds.x,
|
||||||
y: initialBounds.y
|
y: initialBounds.y
|
||||||
|
|||||||
17
src/renderer/controllers/audio-tracks-controller.js
Normal file
17
src/renderer/controllers/audio-tracks-controller.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const { dispatch } = require('../lib/dispatcher')
|
||||||
|
|
||||||
|
module.exports = class AudioTracksController {
|
||||||
|
constructor (state) {
|
||||||
|
this.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAudioTrack (ix) {
|
||||||
|
this.state.playing.audioTracks.selectedIndex = ix
|
||||||
|
dispatch('skip', 0.2) // HACK: hardcoded seek value for smooth audio change
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAudioTracksMenu () {
|
||||||
|
const audioTracks = this.state.playing.audioTracks
|
||||||
|
audioTracks.showMenu = !audioTracks.showMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,6 +105,11 @@ function getDefaultPlayState () {
|
|||||||
selectedIndex: -1, /* current subtitle track */
|
selectedIndex: -1, /* current subtitle track */
|
||||||
showMenu: false /* popover menu, above the video */
|
showMenu: false /* popover menu, above the video */
|
||||||
},
|
},
|
||||||
|
audioTracks: {
|
||||||
|
tracks: [],
|
||||||
|
selectedIndex: 0, /* current audio track */
|
||||||
|
showMenu: false /* popover menu, above the video */
|
||||||
|
},
|
||||||
aspectRatio: 0 /* aspect ratio of the video */
|
aspectRatio: 0 /* aspect ratio of the video */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ function onState (err, _state) {
|
|||||||
const SubtitlesController = require('./controllers/subtitles-controller')
|
const SubtitlesController = require('./controllers/subtitles-controller')
|
||||||
return new SubtitlesController(state)
|
return new SubtitlesController(state)
|
||||||
}),
|
}),
|
||||||
|
audioTracks: createGetter(() => {
|
||||||
|
const AudioTracksController = require('./controllers/audio-tracks-controller')
|
||||||
|
return new AudioTracksController(state)
|
||||||
|
}),
|
||||||
torrent: createGetter(() => {
|
torrent: createGetter(() => {
|
||||||
const TorrentController = require('./controllers/torrent-controller')
|
const TorrentController = require('./controllers/torrent-controller')
|
||||||
return new TorrentController(state)
|
return new TorrentController(state)
|
||||||
@@ -281,6 +285,10 @@ const dispatchHandlers = {
|
|||||||
checkForSubtitles: () => controllers.subtitles().checkForSubtitles(),
|
checkForSubtitles: () => controllers.subtitles().checkForSubtitles(),
|
||||||
addSubtitles: (files, autoSelect) => controllers.subtitles().addSubtitles(files, autoSelect),
|
addSubtitles: (files, autoSelect) => controllers.subtitles().addSubtitles(files, autoSelect),
|
||||||
|
|
||||||
|
// Audio Tracks
|
||||||
|
selectAudioTrack: (index) => controllers.audioTracks().selectAudioTrack(index),
|
||||||
|
toggleAudioTracksMenu: () => controllers.audioTracks().toggleAudioTracksMenu(),
|
||||||
|
|
||||||
// Local media: <video>, <audio>, external players
|
// Local media: <video>, <audio>, external players
|
||||||
mediaStalled: () => controllers.media().mediaStalled(),
|
mediaStalled: () => controllers.media().mediaStalled(),
|
||||||
mediaError: (err) => controllers.media().mediaError(err),
|
mediaError: (err) => controllers.media().mediaError(err),
|
||||||
|
|||||||
@@ -97,6 +97,13 @@ function renderMedia (state) {
|
|||||||
delete file.selectedSubtitle
|
delete file.selectedSubtitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Switch to selected audio track
|
||||||
|
const audioTracks = mediaElement.audioTracks || []
|
||||||
|
for (let j = 0; j < audioTracks.length; j++) {
|
||||||
|
const isSelectedTrack = j === state.playing.audioTracks.selectedIndex
|
||||||
|
audioTracks[j].enabled = isSelectedTrack
|
||||||
|
}
|
||||||
|
|
||||||
state.playing.volume = mediaElement.volume
|
state.playing.volume = mediaElement.volume
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +164,20 @@ function renderMedia (state) {
|
|||||||
height: video.videoHeight
|
height: video.videoHeight
|
||||||
}
|
}
|
||||||
dispatch('setDimensions', dimensions)
|
dispatch('setDimensions', dimensions)
|
||||||
|
|
||||||
|
if (video.audioTracks) {
|
||||||
|
// set audioTracks
|
||||||
|
const tracks = []
|
||||||
|
for (let i = 0; i < video.audioTracks.length; i++) {
|
||||||
|
tracks.push({
|
||||||
|
label: video.audioTracks[i].label || `Track ${i + 1}`,
|
||||||
|
language: video.audioTracks[i].language
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
state.playing.audioTracks.tracks = tracks
|
||||||
|
state.playing.audioTracks.selectedIndex = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnded (e) {
|
function onEnded (e) {
|
||||||
@@ -475,6 +496,27 @@ function renderSubtitleOptions (state) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderAudioTrackOptions (state) {
|
||||||
|
const audioTracks = state.playing.audioTracks
|
||||||
|
if (!audioTracks.tracks.length || !audioTracks.showMenu) return
|
||||||
|
|
||||||
|
const items = audioTracks.tracks.map(function (track, ix) {
|
||||||
|
const isSelected = state.playing.audioTracks.selectedIndex === ix
|
||||||
|
return (
|
||||||
|
<li key={ix} onClick={dispatcher('selectAudioTrack', ix)}>
|
||||||
|
<i className='icon'>{'radio_button_' + (isSelected ? 'checked' : 'unchecked')}</i>
|
||||||
|
{track.label}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul key='audio-track-options' className='options-list'>
|
||||||
|
{items}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function renderPlayerControls (state) {
|
function renderPlayerControls (state) {
|
||||||
const positionPercent = 100 * state.playing.currentTime / state.playing.duration
|
const positionPercent = 100 * state.playing.currentTime / state.playing.duration
|
||||||
const playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 3px)' }
|
const playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 3px)' }
|
||||||
@@ -483,6 +525,9 @@ function renderPlayerControls (state) {
|
|||||||
: state.playing.subtitles.selectedIndex >= 0
|
: state.playing.subtitles.selectedIndex >= 0
|
||||||
? 'active'
|
? 'active'
|
||||||
: ''
|
: ''
|
||||||
|
const multiAudioClass = state.playing.audioTracks.tracks.length > 1
|
||||||
|
? 'active'
|
||||||
|
: 'disabled'
|
||||||
const prevClass = Playlist.hasPrevious(state) ? '' : 'disabled'
|
const prevClass = Playlist.hasPrevious(state) ? '' : 'disabled'
|
||||||
const nextClass = Playlist.hasNext(state) ? '' : 'disabled'
|
const nextClass = Playlist.hasNext(state) ? '' : 'disabled'
|
||||||
|
|
||||||
@@ -547,6 +592,14 @@ function renderPlayerControls (state) {
|
|||||||
>
|
>
|
||||||
closed_caption
|
closed_caption
|
||||||
</i>
|
</i>
|
||||||
|
), (
|
||||||
|
<i
|
||||||
|
key='audio-tracks'
|
||||||
|
className={'icon multi-audio float-right ' + multiAudioClass}
|
||||||
|
onClick={handleAudioTracks}
|
||||||
|
>
|
||||||
|
library_music
|
||||||
|
</i>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,6 +706,7 @@ function renderPlayerControls (state) {
|
|||||||
{elements}
|
{elements}
|
||||||
{renderCastOptions(state)}
|
{renderCastOptions(state)}
|
||||||
{renderSubtitleOptions(state)}
|
{renderSubtitleOptions(state)}
|
||||||
|
{renderAudioTrackOptions(state)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -696,6 +750,10 @@ function renderPlayerControls (state) {
|
|||||||
dispatch('toggleSubtitlesMenu')
|
dispatch('toggleSubtitlesMenu')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAudioTracks (e) {
|
||||||
|
dispatch('toggleAudioTracksMenu')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
||||||
|
|||||||
@@ -623,6 +623,11 @@ body.drag .app::after {
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player .controls .icon.multi-audio {
|
||||||
|
font-size: 26px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.player .controls .icon.fullscreen {
|
.player .controls .icon.fullscreen {
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
|
|||||||
Reference in New Issue
Block a user