Sunday, 2 April 2017

How to send subtitles from expressJs to VueJs in a streaming application?

I'm building a movie streaming single page web application (it fetches torrent magnet url, and streams it to a custom video player) using Node/expressJS in back, VueJs 2.0 in front, with Vue-Router. This is all working great. However, I'm now trying to add subtitles to the player and am getting stuck a little.

This is my middleware used to download subtitles and convert them to VTT format, using opensubtitles-api and srt-to-vtt npm packages.

This is my player.vue Vue component

<template>
    <div class="player" ref='player' @mousemove="showControls" :class="{'playing': isPlaying, 'loading': videoLoading}">
        <span class="player__loader" v-if="videoLoading || !initialized">
            <i class='nc-icon-outline loader_circle-04 spin'></i>
        </span>
        <video class="player__video viewer" @click="togglePlay" @dblclick.prevent="toggleFullscreen" @timeupdate="handleProgress" @loadeddata="initVideos" ref="video" @seeking="videoLoading = true" @seeked="videoLoading = false" @waiting="videoLoading = true">
            <source :src="'http://localhost:5000/api/stream/' + movieid" type="video/mp4">
            <track :src="'http://localhost:5000/api/subtitles/' + movieid" kind="subtitles" srclang="en" default></track>
        </video>
        <div class="player__controls" ref="controls" v-if="initialized">
            <div class="progress" @click="scrub"><span class="progress__filled" :style="{'flex-basis': percent + '%'}"></span></div>
            <div class='player__actions'>
                <div class="utils-flex-center">
                    <button class="player__button" data-skip="-10">
                        <i class='nc-icon-glyph arrows-1_triangle-left-63'></i>
                    </button>
                    <button class="player__button" title="Toggle Play" @click="togglePlay">
                        <i class='nc-icon-glyph' :class="isPlaying ? 'media-1_button-pause' : 'media-1_button-play'"></i>
                    </button>
                    <button class="player__button" data-skip="10">
                        <i class='nc-icon-glyph arrows-1_triangle-right-62'></i>
                    </button>
                    <input type="range" name="volume" class="player__slider" min="0" max="1" step="0.05" value="1" @input="soundUpdate" @change="soundUpdate">
                </div>
                <div class="utils-flex-center">
                    <button class="player__button"><i class='nc-icon-glyph ui-3_menu-right'></i></button>
                    <button class="player__button" @click="toggleFullscreen"><i class='nc-icon-glyph arrows-2_zoom'></i></button>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'player',
        data () {
            return {
                videoUri: null,
                isPlaying: false,
                initialized: false,
                duration: 0,
                currentTime: 0,
                el: null,
                videoLoading: false,
                controlsTimeout: null,
                discretePos: 0
            }
        },
        computed: {
            percent: function() { return (this.currentTime / this.duration) * 100 || 0 }
        },
        props: {
            movieid: {
                required: false,
                type: String
            }
        },
        methods: {
            showControls: function(e) {
                let controls = this.$refs.player,
                _this = this;
                if (!controls)
                    return ;
                else if (this.discretePos >= e.pageX + e.pageY + 10 || this.discretePos <= e.pageX + e.pageY - 10) {
                    controls.classList.remove('discrete')
                }
                if (this.controlsTimeout)
                    clearTimeout(this.controlsTimeout);
                this.controlsTimeout = setTimeout(function() {
                    controls.classList.add('discrete')
                    _this.discretePos = e.pageX + e.pageY
                }, 500);
            },
            initVideos: function(e) {
                let video = this.el = e.currentTarget;
                this.duration = video.duration
                this.isPlaying = !video.paused
                this.initialized = true
            },
            togglePlay: function() {
                let video = this.el;
                this.isPlaying = !this.isPlaying
                const method = this.isPlaying ? 'play' : 'pause';
                console.log(method + ' called')
                video[method]();
            },
            handleProgress: function(e) {
                this.currentTime = e.currentTarget.currentTime
                this.videoLoading = false;
            },
            scrub: function(e) {
                const scrubTime = (e.offsetX / e.currentTarget.offsetWidth) * this.duration;
                this.currentTime = this.el.currentTime = scrubTime;
            },
            soundUpdate: function(e) {
                console.log(e.currentTarget.value)
                this.el.volume = e.currentTarget.value
            },
            toggleFullscreen: function(player) {
                var element = this.$refs.player
                if (document.webkitIsFullScreen)
                    document.webkitCancelFullScreen()
                else if (element.mozRequestFullScreen) {
                    element.mozRequestFullScreen();
                } else if (element.webkitRequestFullScreen) {
                    element.webkitRequestFullScreen();
                }
            }
        }
    };
</script>

As you can see, I'm getting my video source from my route /api/stream, and am trying to do the same with subtitles, using /api/subtitles.

My route is pretty simple, and calls this express middleware:

//SubsController.js

module.exports = {
getSubtitles: function(req, res, next) {
    var download = function (url, dest, cb) {
        console.log('downloading subtitles...')
        let file = fs.createWriteStream(dest);
        let request = http.get(url, (response) => {
            response.pipe(file);
            file.on('finish', () => {
                file.close(cb);
            })
        })
    }
    OpenSubtitles.login().then(resp => {
        OpenSubtitles.search({
            sublanguageid: 'eng',       // Can be an array.join, 'all', or be omitted. 
            extensions: ['srt', 'vtt'], // Accepted extensions, defaults to 'srt'. 
            imdbid: req.params.movieid,           // 'tt528809' is fine too. 
        }).then(subtitles => {
            let url = subtitles.en.url; //CARE LANGUE (fr ?)
            let dest = '/goinfre/pd/subtitles'
            download(url, dest, function() {
                console.log('subtitles downloaded.')
                fs.createReadStream(dest)
                .pipe(srt2vtt())
                .pipe(res)
                return next()
            });
        })
    }).catch(err => {
        console.log(err)
        return next('problem downloading subtitles')
    });
}

which will use opensubtitles-api and srt-to-vtt npm packages to download the proper srt subtitle, then convert it to the VTT format.

My question is, what would be the best way to achieve this? Do I need to think differently?

I was thinking about res.sendFile but I don't think it's what I want to do.

If you need more code to fully understand this problem, just let me know!

Thanks for reading.



via pds42

No comments:

Post a Comment