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",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn-promise/-/cross-spawn-promise-0.10.1.tgz",
|
||||
"integrity": "sha1-25y0xQxgtyoVvgSbeBIs44LYexA=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^5.1.0"
|
||||
},
|
||||
@@ -1963,6 +1964,7 @@
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
@@ -2497,6 +2499,7 @@
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.7.3.tgz",
|
||||
"integrity": "sha512-l4chYFTWr6uWODKYUXeC+Z4tqGa3b8e+Y2WUBf3F7Ruv6yYzZ+Ccic65oXreeotx09B7sUx1KTuwXRsRJHKlMw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"asar": "^2.0.1",
|
||||
"cross-spawn-promise": "^0.10.1",
|
||||
@@ -2513,6 +2516,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
@@ -2521,6 +2525,7 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -2570,7 +2575,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-2.0.0.tgz",
|
||||
"integrity": "sha512-kf7+/t8XIp1I6LIV9v6K38rBHzmY6bUr3TunJZKdlIKQ7j6wyjjpgbpxSBcg3S7pgzq1kkgCYZvpr8CsLFVivw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"electron-installer-common": "^0.7.1",
|
||||
@@ -2584,7 +2589,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
@@ -2593,7 +2598,7 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -9062,6 +9067,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.0.2.tgz",
|
||||
"integrity": "sha512-zl71nFWjPKW2KXs+73gEk8RmqvtAeXPxhWDkTUoa3MSMkjq3I+9OeknjF178MQoMYsdqL730hfzvNfEkePxq9Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tmp": "0.1.0"
|
||||
}
|
||||
@@ -10045,7 +10051,8 @@
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"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": {
|
||||
"version": "0.0.2",
|
||||
|
||||
@@ -44,7 +44,8 @@ function init (state, options) {
|
||||
useContentSize: true, // Specify web page size without OS chrome
|
||||
width: initialBounds.width,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
nodeIntegration: true,
|
||||
experimentalFeatures: true
|
||||
},
|
||||
x: initialBounds.x,
|
||||
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 */
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ function onState (err, _state) {
|
||||
const SubtitlesController = require('./controllers/subtitles-controller')
|
||||
return new SubtitlesController(state)
|
||||
}),
|
||||
audioTracks: createGetter(() => {
|
||||
const AudioTracksController = require('./controllers/audio-tracks-controller')
|
||||
return new AudioTracksController(state)
|
||||
}),
|
||||
torrent: createGetter(() => {
|
||||
const TorrentController = require('./controllers/torrent-controller')
|
||||
return new TorrentController(state)
|
||||
@@ -281,6 +285,10 @@ const dispatchHandlers = {
|
||||
checkForSubtitles: () => controllers.subtitles().checkForSubtitles(),
|
||||
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
|
||||
mediaStalled: () => controllers.media().mediaStalled(),
|
||||
mediaError: (err) => controllers.media().mediaError(err),
|
||||
|
||||
@@ -97,6 +97,13 @@ function renderMedia (state) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -157,6 +164,20 @@ function renderMedia (state) {
|
||||
height: video.videoHeight
|
||||
}
|
||||
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) {
|
||||
@@ -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) {
|
||||
const positionPercent = 100 * state.playing.currentTime / state.playing.duration
|
||||
const playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 3px)' }
|
||||
@@ -483,6 +525,9 @@ function renderPlayerControls (state) {
|
||||
: state.playing.subtitles.selectedIndex >= 0
|
||||
? 'active'
|
||||
: ''
|
||||
const multiAudioClass = state.playing.audioTracks.tracks.length > 1
|
||||
? 'active'
|
||||
: 'disabled'
|
||||
const prevClass = Playlist.hasPrevious(state) ? '' : 'disabled'
|
||||
const nextClass = Playlist.hasNext(state) ? '' : 'disabled'
|
||||
|
||||
@@ -547,6 +592,14 @@ function renderPlayerControls (state) {
|
||||
>
|
||||
closed_caption
|
||||
</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}
|
||||
{renderCastOptions(state)}
|
||||
{renderSubtitleOptions(state)}
|
||||
{renderAudioTrackOptions(state)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -696,6 +750,10 @@ function renderPlayerControls (state) {
|
||||
dispatch('toggleSubtitlesMenu')
|
||||
}
|
||||
}
|
||||
|
||||
function handleAudioTracks (e) {
|
||||
dispatch('toggleAudioTracksMenu')
|
||||
}
|
||||
}
|
||||
|
||||
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
||||
|
||||
@@ -623,6 +623,11 @@ body.drag .app::after {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.player .controls .icon.multi-audio {
|
||||
font-size: 26px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.player .controls .icon.fullscreen {
|
||||
font-size: 26px;
|
||||
margin-right: 15px;
|
||||
|
||||
Reference in New Issue
Block a user