From 04318d7580d93ddbb75e0f8fbf73792a218bfe94 Mon Sep 17 00:00:00 2001 From: grunjol Date: Fri, 15 Apr 2016 01:47:50 -0300 Subject: [PATCH] Add multiple subtitles support (#406) * add multiple subtitles support * cleanup and remove log --- package.json | 1 + renderer/index.css | 24 ++++++++++++++++++ renderer/index.js | 34 +++++++++++++++++++++++++- renderer/views/player.js | 53 +++++++++++++++++++++++++++++++--------- 4 files changed, 99 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 9a69f880..2d78102b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "electron-localshortcut": "^0.6.0", "electron-prebuilt": "0.37.5", "hyperx": "^2.0.2", + "languagedetect": "^1.1.1", "main-loop": "^3.2.0", "mkdirp": "^0.5.1", "musicmetadata": "^2.0.2", diff --git a/renderer/index.css b/renderer/index.css index 9e0d9016..8ba15525 100644 --- a/renderer/index.css +++ b/renderer/index.css @@ -707,6 +707,7 @@ body.drag .app::after { margin-top: 8px !important; } +.player-controls .closed-captions.active, .player-controls .device.active { color: #9af; } @@ -789,6 +790,29 @@ body.drag .app::after { font-weight: bold; } +/* + * Subtitles list + */ + +.subtitles-list { + position: fixed; + background: rgba(40, 40, 40, 0.8); + min-width: 100px; + bottom: 45px; + right: 3px; + transition: opacity 0.15s ease-out; + padding: 5px 10px; + border-radius: 3px; + margin: 0; + list-style-type: none; + color: rgba(255, 255, 255, 0.8); +} + +.subtitles-list i { + font-size: 11px; /* make the cast icons less huge */ + margin-right: 4px !important; +} + /* * MEDIA OVERLAY / AUDIO DETAILS */ diff --git a/renderer/index.js b/renderer/index.js index 2cf28fa8..a64d2e18 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -8,6 +8,7 @@ var fs = require('fs') var mainLoop = require('main-loop') var path = require('path') var srtToVtt = require('srt-to-vtt') +var LanguageDetect = require('languagedetect') var createElement = require('virtual-dom/create-element') var diff = require('virtual-dom/diff') @@ -267,6 +268,12 @@ function dispatch (action, ...args) { if (action === 'openSubtitles') { openSubtitles() } + if (action === 'selectSubtitle') { + selectSubtitle(args[0] /* label */) + } + if (action === 'showSubtitles') { + showSubtitles() + } if (action === 'mediaStalled') { state.playing.isStalled = true } @@ -553,17 +560,42 @@ function addSubtitle (file) { // Set the cue text position so it appears above the player controls. // The only way to change cue text position is by modifying the VTT. It is not // possible via CSS. + var langDetected = (new LanguageDetect()).detect(buf.toString().replace(/(.*-->.*)/g, ''), 2) + langDetected = langDetected.length ? langDetected[0][0] : 'subtitle' + langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1) var subtitles = Buffer(buf.toString().replace(/(-->.*)/g, '$1 line:88%')) var track = { buffer: 'data:text/vtt;base64,' + subtitles.toString('base64'), - language: 'Language ' + state.playing.subtitles.tracks.length, + label: langDetected, selected: true } + state.playing.subtitles.tracks.forEach(function (trackItem) { + trackItem.selected = false + if (trackItem.label === track.label) { + track.label = Number.isNaN(track.label.slice(-1)) + ? track.label + ' 2' + : track.label.slice(0, -1) + (parseInt(track.label.slice(-1)) + 1) + } + }) + state.playing.subtitles.change = track.label state.playing.subtitles.tracks.push(track) state.playing.subtitles.enabled = true })) } +function selectSubtitle (label) { + state.playing.subtitles.tracks.forEach(function (track) { + track.selected = (track.label === label) + }) + state.playing.subtitles.enabled = !!label + state.playing.subtitles.change = label + state.playing.subtitles.show = false +} + +function showSubtitles () { + state.playing.subtitles.show = !state.playing.subtitles.show +} + // Starts downloading and/or seeding a given torrentSummary. Returns WebTorrent object function startTorrentingSummary (torrentSummary) { var s = torrentSummary diff --git a/renderer/views/player.js b/renderer/views/player.js index 94b91d66..e1590e34 100644 --- a/renderer/views/player.js +++ b/renderer/views/player.js @@ -48,6 +48,16 @@ function renderMedia (state) { state.playing.setVolume = null } + // fix textTrack cues not been removed rerender + if (state.playing.subtitles.change) { + var tracks = mediaElement.textTracks + for (var j = 0; j < tracks.length; j++) { + // mode is not an attribute, only available on DOM + tracks[j].mode = (tracks[j].label === state.playing.subtitles.change) ? 'showing' : 'hidden' + } + state.playing.subtitles.change = null + } + state.playing.currentTime = mediaElement.currentTime state.playing.duration = mediaElement.duration state.playing.volume = mediaElement.volume @@ -59,13 +69,12 @@ function renderMedia (state) { if (state.playing.subtitles.enabled && state.playing.subtitles.tracks.length > 0) { for (var i = 0; i < state.playing.subtitles.tracks.length; i++) { var track = state.playing.subtitles.tracks[i] - trackTags.push(hx` + ${track.selected ? 'default' : ''} + label=${track.label} + type='subtitles' + src=${track.buffer}> `) } } @@ -229,9 +238,27 @@ function renderCastScreen (state) { ` } +function renderSubtitlesOptions (state) { + var subtitles = state.playing.subtitles + if (subtitles.tracks.length && subtitles.show) { + return hx` + ${subtitles.tracks.map(function (w, i) { + return hx`
  • ${w.selected ? 'radio_button_checked' : 'radio_button_unchecked'}${w.label}
  • ` + })} +
  • ${!subtitles.enabled ? 'radio_button_checked' : 'radio_button_unchecked'}None
  • + + ` + } +} + function renderPlayerControls (state) { var positionPercent = 100 * state.playing.currentTime / state.playing.duration var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' } + var captionsClass = state.playing.subtitles.tracks.length === 0 + ? 'disabled' + : state.playing.subtitles.enabled + ? 'active' + : '' var elements = [ hx` @@ -256,7 +283,7 @@ function renderPlayerControls (state) { // show closed captions icon elements.push(hx` closed_captions @@ -369,7 +396,12 @@ function renderPlayerControls (state) { `) - return hx`
    ${elements}
    ` + return hx` +
    + ${elements} + ${renderSubtitlesOptions(state)} +
    + ` // Handles a click or drag to scrub (jump to another position in the video) function handleScrub (e) { @@ -414,14 +446,11 @@ function renderPlayerControls (state) { } function handleSubtitles (e) { - if (!state.playing.subtitles.tracks.length) { + if (!state.playing.subtitles.tracks.length || e.ctrlKey || e.metaKey) { // if no subtitles available select it dispatch('openSubtitles') } else { - // TODO: Show subtitles selector / disable - // dispatch('showSubtitlesMenu') - // meanwhile, just enable/disable - state.playing.subtitles.enabled = !state.playing.subtitles.enabled + dispatch('showSubtitles') } } }