forked from rc/aircox
		
	work on player: integrate vuejs + noscript; remove TemplateMixin for Component and ExposedData; rewrite most of the player; clean up files; do lot of other things
This commit is contained in:
		
							
								
								
									
										26
									
								
								aircox_cms/static/aircox_cms/js/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								aircox_cms/static/aircox_cms/js/bootstrap.js
									
									
									
									
										vendored
									
									
								
							@ -13,3 +13,29 @@ window.addEventListener('scroll', function(e) {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// TODO: later get rid of it in order to use Vue stuff
 | 
			
		||||
/// Helper to provide a tab+panel functionnality; the tab and the selected
 | 
			
		||||
/// element will have an attribute "selected".
 | 
			
		||||
/// We assume a common ancestor between tab and panel at a maximum level
 | 
			
		||||
/// of 2.
 | 
			
		||||
/// * tab: corresponding tab
 | 
			
		||||
/// * panel_selector is used to select the right panel object.
 | 
			
		||||
function select_tab(tab, panel_selector) {
 | 
			
		||||
    var parent = tab.parentNode.parentNode;
 | 
			
		||||
    var panel = parent.querySelector(panel_selector);
 | 
			
		||||
 | 
			
		||||
    // unselect
 | 
			
		||||
    var qs = parent.querySelectorAll('*[selected]');
 | 
			
		||||
    for(var i = 0; i < qs.length; i++)
 | 
			
		||||
        if(qs[i] != tab && qs[i] != panel)
 | 
			
		||||
            qs[i].removeAttribute('selected');
 | 
			
		||||
 | 
			
		||||
    panel.setAttribute('selected', 'true');
 | 
			
		||||
    tab.setAttribute('selected', 'true');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										694
									
								
								aircox_cms/static/aircox_cms/js/player.js
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										694
									
								
								aircox_cms/static/aircox_cms/js/player.js
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@ -1,450 +1,314 @@
 | 
			
		||||
// TODO
 | 
			
		||||
// - live streams as item;
 | 
			
		||||
// - add to playlist button
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
/// Return a human-readable string from seconds
 | 
			
		||||
function duration_str(seconds) {
 | 
			
		||||
    seconds = Math.floor(seconds);
 | 
			
		||||
    var hours = Math.floor(seconds / 3600);
 | 
			
		||||
    seconds -= hours * 3600;
 | 
			
		||||
    var minutes = Math.floor(seconds / 60);
 | 
			
		||||
    seconds -= minutes * 60;
 | 
			
		||||
 | 
			
		||||
    var str = hours ? (hours < 10 ? '0' + hours : hours) + ':' : '';
 | 
			
		||||
    str += (minutes < 10 ? '0' + minutes : minutes) + ':';
 | 
			
		||||
    str += (seconds < 10 ? '0' + seconds : seconds);
 | 
			
		||||
    return str;
 | 
			
		||||
}
 | 
			
		||||
/*  Implementation status: -- TODO
 | 
			
		||||
 *  - actions:
 | 
			
		||||
 *      - add to user playlist
 | 
			
		||||
 *      - go to detail
 | 
			
		||||
 *      - remove from playlist: for user playlist
 | 
			
		||||
 *  - save sound infos:
 | 
			
		||||
 *      - while playing: save current position
 | 
			
		||||
 *      - otherwise: remove from localstorage
 | 
			
		||||
 *      - save playlist in localstorage
 | 
			
		||||
 *  - proper design
 | 
			
		||||
 *  - mini-button integration in lists (list of diffusion articles)
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function Sound(title, detail, duration, streams, cover, on_air) {
 | 
			
		||||
    this.title = title;
 | 
			
		||||
    this.detail = detail;
 | 
			
		||||
    this.duration = duration;
 | 
			
		||||
    this.streams = streams.splice ? streams.sort() : [streams];
 | 
			
		||||
    this.cover = cover;
 | 
			
		||||
    this.on_air = on_air;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Sound.prototype = {
 | 
			
		||||
    title: '',
 | 
			
		||||
    detail: '',
 | 
			
		||||
    streams: undefined,
 | 
			
		||||
    duration: undefined,
 | 
			
		||||
    cover: undefined,
 | 
			
		||||
    on_air: false,
 | 
			
		||||
 | 
			
		||||
    item: undefined,
 | 
			
		||||
    position_item: undefined,
 | 
			
		||||
 | 
			
		||||
    get seekable() {
 | 
			
		||||
        return this.duration != undefined;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    make_item: function(playlist, base_item) {
 | 
			
		||||
        if(this.item)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var item = base_item.cloneNode(true);
 | 
			
		||||
        item.removeAttribute('style');
 | 
			
		||||
 | 
			
		||||
        item.querySelector('.title').innerHTML = this.title;
 | 
			
		||||
        if(this.seekable)
 | 
			
		||||
            item.querySelector('.duration').innerHTML =
 | 
			
		||||
                duration_str(this.duration);
 | 
			
		||||
        if(this.detail)
 | 
			
		||||
            item.querySelector('.detail').href = this.detail;
 | 
			
		||||
        if(playlist.player.show_cover && this.cover)
 | 
			
		||||
            item.querySelector('img.cover').src = this.cover;
 | 
			
		||||
 | 
			
		||||
        item.sound = this;
 | 
			
		||||
        this.item = item;
 | 
			
		||||
        this.position_item = item.querySelector('.position');
 | 
			
		||||
 | 
			
		||||
        // events
 | 
			
		||||
        var self = this;
 | 
			
		||||
        item.querySelector('.action.remove').addEventListener(
 | 
			
		||||
            'click', function(event) { playlist.remove(self); }, false
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        item.querySelector('.action.add').addEventListener(
 | 
			
		||||
            'click', function(event) {
 | 
			
		||||
                player.playlist.add(new Sound(
 | 
			
		||||
                    title = self.title,
 | 
			
		||||
                    detail = self.detail,
 | 
			
		||||
                    duration = self.duration,
 | 
			
		||||
                    streams = self.streams,
 | 
			
		||||
                    cover = self.cover,
 | 
			
		||||
                    on_air = self.on_air
 | 
			
		||||
                ));
 | 
			
		||||
            }, false
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        item.addEventListener('click', function(event) {
 | 
			
		||||
            if(event.target.className.indexOf('action') != -1)
 | 
			
		||||
                return;
 | 
			
		||||
            playlist.select(self, true)
 | 
			
		||||
        }, false);
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
var State = Object.freeze({
 | 
			
		||||
    Stop: Symbol('Stop'),
 | 
			
		||||
    Loading: Symbol('Loading'),
 | 
			
		||||
    Play: Symbol('Play'),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function Playlist(player) {
 | 
			
		||||
    this.player = player;
 | 
			
		||||
    this.playlist = player.player.querySelector('.playlist');
 | 
			
		||||
    this.item_ = player.player.querySelector('.playlist .item');
 | 
			
		||||
    this.sounds = []
 | 
			
		||||
}
 | 
			
		||||
class Track {
 | 
			
		||||
    // Create a track with the given data.
 | 
			
		||||
    // If url and interval are given, use them to retrieve regularely
 | 
			
		||||
    // the track informations
 | 
			
		||||
    constructor(data) {
 | 
			
		||||
        Object.assign(this, data);
 | 
			
		||||
 | 
			
		||||
Playlist.prototype = {
 | 
			
		||||
    on_air: undefined,
 | 
			
		||||
    sounds: undefined,
 | 
			
		||||
    sound: undefined,
 | 
			
		||||
 | 
			
		||||
    /// Find a sound by its streams, and return it if found
 | 
			
		||||
    find: function(streams) {
 | 
			
		||||
        streams = streams.splice ? streams.sort() : streams;
 | 
			
		||||
 | 
			
		||||
        return this.sounds.find(function(sound) {
 | 
			
		||||
            // comparing array
 | 
			
		||||
            if(!sound.streams || sound.streams.length != streams.length)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            for(var i = 0; i < streams.length; i++)
 | 
			
		||||
                if(sound.streams[i] != streams[i])
 | 
			
		||||
                    return false;
 | 
			
		||||
            return true
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    add: function(sound, container, position) {
 | 
			
		||||
        var sound_ = this.find(sound.streams);
 | 
			
		||||
        if(sound_)
 | 
			
		||||
            return sound_;
 | 
			
		||||
 | 
			
		||||
        if(sound.on_air)
 | 
			
		||||
            this.on_air = sound;
 | 
			
		||||
 | 
			
		||||
        sound.make_item(this, this.item_);
 | 
			
		||||
 | 
			
		||||
        container = container || this.playlist;
 | 
			
		||||
        if(position != undefined) {
 | 
			
		||||
            container.insertBefore(sound.item, container.children[position]);
 | 
			
		||||
            this.sounds.splice(position, 0, sound);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            container.appendChild(sound.item);
 | 
			
		||||
            this.sounds.push(sound);
 | 
			
		||||
        }
 | 
			
		||||
        this.save();
 | 
			
		||||
        return sound;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    remove: function(sound) {
 | 
			
		||||
        var index = this.sounds.indexOf(sound);
 | 
			
		||||
        if(index != -1)
 | 
			
		||||
            this.sounds.splice(index,1);
 | 
			
		||||
        this.playlist.removeChild(sound.item);
 | 
			
		||||
        this.save();
 | 
			
		||||
 | 
			
		||||
        if(this.sound == sound) {
 | 
			
		||||
            this.player.stop()
 | 
			
		||||
            this.next(false);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    select: function(sound, play = true) {
 | 
			
		||||
        this.player.playlist = this;
 | 
			
		||||
        if(this.sound == sound) {
 | 
			
		||||
            if(play)
 | 
			
		||||
                this.player.play();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(this.sound)
 | 
			
		||||
            this.unselect(this.sound);
 | 
			
		||||
        this.sound = sound;
 | 
			
		||||
 | 
			
		||||
        // audio
 | 
			
		||||
        this.player.load_sound(this.sound);
 | 
			
		||||
 | 
			
		||||
        // attributes
 | 
			
		||||
        var container = this.player.player;
 | 
			
		||||
        sound.item.setAttribute('selected', 'true');
 | 
			
		||||
 | 
			
		||||
        if(!sound.on_air)
 | 
			
		||||
            sound.item.querySelector('.content').parentNode.appendChild(
 | 
			
		||||
                this.player.progress.item //,
 | 
			
		||||
                // sound.item.querySelector('.content .duration')
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if(sound.seekable)
 | 
			
		||||
            container.setAttribute('seekable', 'true');
 | 
			
		||||
        else
 | 
			
		||||
            container.removeAttribute('seekable');
 | 
			
		||||
 | 
			
		||||
        // play
 | 
			
		||||
        if(play)
 | 
			
		||||
            this.player.play();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    unselect: function(sound) {
 | 
			
		||||
        sound.item.removeAttribute('selected');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    next: function(play = true) {
 | 
			
		||||
        var index = this.sounds.indexOf(this.sound);
 | 
			
		||||
        if(index < 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        index++;
 | 
			
		||||
        if(index < this.sounds.length)
 | 
			
		||||
            this.select(this.sounds[index]);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // storage
 | 
			
		||||
    save: function() {
 | 
			
		||||
        var list = [];
 | 
			
		||||
        for(var i in this.sounds) {
 | 
			
		||||
            var sound = Object.assign({}, this.sounds[i])
 | 
			
		||||
            if(sound.on_air)
 | 
			
		||||
                continue;
 | 
			
		||||
            delete sound.item;
 | 
			
		||||
            list.push(sound);
 | 
			
		||||
        }
 | 
			
		||||
        this.player.store.set('playlist', list);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    load: function() {
 | 
			
		||||
        var list = this.player.store.get('playlist');
 | 
			
		||||
        var container = document.createDocumentFragment();
 | 
			
		||||
        for(var i in list) {
 | 
			
		||||
            var sound = list[i];
 | 
			
		||||
            sound = new Sound(sound.title, sound.detail, sound.duration,
 | 
			
		||||
                              sound.streams, sound.cover, sound.on_air)
 | 
			
		||||
            this.add(sound, container)
 | 
			
		||||
        }
 | 
			
		||||
        this.playlist.appendChild(container);
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var ActivePlayer = null;
 | 
			
		||||
 | 
			
		||||
function Player(id, on_air_url, show_cover) {
 | 
			
		||||
    this.id = id;
 | 
			
		||||
    this.on_air_url = on_air_url;
 | 
			
		||||
    this.show_cover = show_cover;
 | 
			
		||||
 | 
			
		||||
    this.store = new Store('player_' + id);
 | 
			
		||||
 | 
			
		||||
    // html sounds
 | 
			
		||||
    this.player = document.getElementById(id);
 | 
			
		||||
    this.audio = this.player.querySelector('audio');
 | 
			
		||||
    this.on_air = this.player.querySelector('.on_air');
 | 
			
		||||
    this.progress = {
 | 
			
		||||
        item: this.player.querySelector('.controls .progress'),
 | 
			
		||||
        bar: this.player.querySelector('.controls .progress progress'),
 | 
			
		||||
        duration: this.player.querySelector('.controls .progress .duration')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.controls = {
 | 
			
		||||
        single: this.player.querySelector('input.single'),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.playlist = new Playlist(this);
 | 
			
		||||
    this.playlist.load();
 | 
			
		||||
 | 
			
		||||
    this.init_events();
 | 
			
		||||
    this.load();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Player.prototype = {
 | 
			
		||||
    /// current item being played
 | 
			
		||||
    sound: undefined,
 | 
			
		||||
    on_air_url: undefined,
 | 
			
		||||
 | 
			
		||||
    get sound() {
 | 
			
		||||
        return this.playlist.sound;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    init_events: function() {
 | 
			
		||||
        var self = this;
 | 
			
		||||
 | 
			
		||||
        function time_from_progress(event) {
 | 
			
		||||
            bounding = self.progress.bar.getBoundingClientRect()
 | 
			
		||||
            offset = (event.clientX - bounding.left);
 | 
			
		||||
            return offset * self.audio.duration / bounding.width;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function update_info() {
 | 
			
		||||
            var progress = self.progress;
 | 
			
		||||
            var pos = self.audio.currentTime;
 | 
			
		||||
            var position = self.sound.position_item;
 | 
			
		||||
 | 
			
		||||
            // progress
 | 
			
		||||
            if(!self.audio || !self.audio.seekable ||
 | 
			
		||||
                    !pos || self.audio.duration == Infinity)
 | 
			
		||||
            {
 | 
			
		||||
                position.innerHTML = '';
 | 
			
		||||
                progress.bar.value = 0;
 | 
			
		||||
                return;
 | 
			
		||||
        if(this.data_url) {
 | 
			
		||||
            if(!this.interval)
 | 
			
		||||
                this.data_url = undefined;
 | 
			
		||||
            if(this.run) {
 | 
			
		||||
                this.run = false;
 | 
			
		||||
                this.start();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            progress.bar.value = pos;
 | 
			
		||||
            progress.bar.max = self.audio.duration;
 | 
			
		||||
            position.innerHTML = duration_str(pos);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        // audio
 | 
			
		||||
        this.audio.addEventListener('playing', function() {
 | 
			
		||||
            self.player.setAttribute('state', 'playing');
 | 
			
		||||
        }, false);
 | 
			
		||||
    start() {
 | 
			
		||||
        if(this.run || !this.interval || !this.data_url)
 | 
			
		||||
            return;
 | 
			
		||||
        this.run = true;
 | 
			
		||||
        this.fetch_data();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        this.audio.addEventListener('pause', function() {
 | 
			
		||||
            self.player.setAttribute('state', 'paused');
 | 
			
		||||
        }, false);
 | 
			
		||||
    stop() {
 | 
			
		||||
        this.run = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        this.audio.addEventListener('loadstart', function() {
 | 
			
		||||
            self.player.setAttribute('state', 'loading');
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
        this.audio.addEventListener('loadeddata', function() {
 | 
			
		||||
            self.player.removeAttribute('state');
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
        this.audio.addEventListener('timeupdate', update_info, false);
 | 
			
		||||
 | 
			
		||||
        this.audio.addEventListener('ended', function() {
 | 
			
		||||
            self.player.removeAttribute('state');
 | 
			
		||||
            if(!self.controls.single.checked)
 | 
			
		||||
                self.playlist.next(true);
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
        // progress
 | 
			
		||||
        progress = this.progress.bar;
 | 
			
		||||
        progress.addEventListener('click', function(event) {
 | 
			
		||||
            self.audio.currentTime = time_from_progress(event);
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            event.stopImmediatePropagation();
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
        progress.addEventListener('mouseout', update_info, false);
 | 
			
		||||
 | 
			
		||||
        progress.addEventListener('mousemove', function(event) {
 | 
			
		||||
            if(self.audio.duration == Infinity || isNaN(self.audio.duration))
 | 
			
		||||
               return;
 | 
			
		||||
 | 
			
		||||
            var pos = time_from_progress(event);
 | 
			
		||||
            var position = self.sound.position_item;
 | 
			
		||||
            position.innerHTML = duration_str(pos);
 | 
			
		||||
        }, false);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    update_on_air: function() {
 | 
			
		||||
        if(!this.on_air_url)
 | 
			
		||||
    fetch_data() {
 | 
			
		||||
        if(!this.run || !this.interval || !this.data_url)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var self = this;
 | 
			
		||||
        window.setTimeout(function() {
 | 
			
		||||
            self.update_on_air();
 | 
			
		||||
        }, 60*5000);
 | 
			
		||||
 | 
			
		||||
        if(!this.playlist.on_air)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var req = new XMLHttpRequest();
 | 
			
		||||
        req.open('GET', this.on_air_url, true);
 | 
			
		||||
        req.open('GET', this.data_url, true);
 | 
			
		||||
        req.onreadystatechange = function() {
 | 
			
		||||
            if(req.readyState != 4 || (req.status != 200 &&
 | 
			
		||||
                    req.status != 0))
 | 
			
		||||
            if(req.readyState != 4 || (req.status && req.status != 200))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if(!req.responseText.length)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var data = JSON.parse(req.responseText)
 | 
			
		||||
            // TODO: more consistent API
 | 
			
		||||
            var data = JSON.parse(req.responseText);
 | 
			
		||||
            if(data.type == 'track')
 | 
			
		||||
                data = {
 | 
			
		||||
                    title: '♫ ' + (data.artist ? data.artist + ' — ' : '') +
 | 
			
		||||
                    name: '♫ ' + (data.artist ? data.artist + ' — ' : '') +
 | 
			
		||||
                           data.title,
 | 
			
		||||
                    url: ''
 | 
			
		||||
                    data_url: ''
 | 
			
		||||
                }
 | 
			
		||||
            else
 | 
			
		||||
                data = {
 | 
			
		||||
                    title: data.title,
 | 
			
		||||
                    info: '',
 | 
			
		||||
                    url: data.url
 | 
			
		||||
                    data_url: data.url
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            var on_air = self.playlist.on_air;
 | 
			
		||||
            on_air = on_air.item.querySelector('.content');
 | 
			
		||||
 | 
			
		||||
            if(data.url)
 | 
			
		||||
                on_air.innerHTML =
 | 
			
		||||
                    '<a href="' + data.url + '">' + data.title + '</a>';
 | 
			
		||||
            else
 | 
			
		||||
                on_air.innerHTML = data.title;
 | 
			
		||||
            Object.assign(self, data);
 | 
			
		||||
        };
 | 
			
		||||
        req.send();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    play: function() {
 | 
			
		||||
        if(ActivePlayer && ActivePlayer != this) {
 | 
			
		||||
            ActivePlayer.stop();
 | 
			
		||||
        }
 | 
			
		||||
        ActivePlayer = this;
 | 
			
		||||
        if(this.run && this.interval)
 | 
			
		||||
            this._trigger_fetch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        if(this.audio.paused)
 | 
			
		||||
            this.audio.play();
 | 
			
		||||
        else
 | 
			
		||||
            this.audio.pause();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    stop: function() {
 | 
			
		||||
        this.audio.pause();
 | 
			
		||||
        this.player.removeAttribute('state');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    __mime_type: function(path) {
 | 
			
		||||
        ext = path.substr(path.lastIndexOf('.')+1);
 | 
			
		||||
        return 'audio/' + ext;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    load_sound: function(sound) {
 | 
			
		||||
        var audio = this.audio;
 | 
			
		||||
        audio.pause();
 | 
			
		||||
 | 
			
		||||
        var sources = audio.querySelectorAll('source');
 | 
			
		||||
        for(var i = 0; i < sources.length; i++)
 | 
			
		||||
            audio.removeChild(sources[i]);
 | 
			
		||||
 | 
			
		||||
        streams = sound.streams;
 | 
			
		||||
        for(var i = 0; i < streams.length; i++) {
 | 
			
		||||
            var source = document.createElement('source');
 | 
			
		||||
            source.src = streams[i];
 | 
			
		||||
            source.type = this.__mime_type(source.src);
 | 
			
		||||
            audio.appendChild(source);
 | 
			
		||||
        }
 | 
			
		||||
        audio.load();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    save: function() {
 | 
			
		||||
        // TODO: move stored sound into playlist
 | 
			
		||||
        this.store.set('player', {
 | 
			
		||||
            single: this.controls.single.checked,
 | 
			
		||||
            sound: this.playlist.sound && this.playlist.sound.streams,
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    load: function() {
 | 
			
		||||
        var data = this.store.get('player');
 | 
			
		||||
        if(!data)
 | 
			
		||||
    _trigger_fetch() {
 | 
			
		||||
        if(!this.run || !this.data_url)
 | 
			
		||||
            return;
 | 
			
		||||
        this.controls.single.checked = data.single;
 | 
			
		||||
        if(data.sound)
 | 
			
		||||
            this.playlist.sound = this.playlist.find(data.sound);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
        var self = this;
 | 
			
		||||
        if(this.interval)
 | 
			
		||||
            window.setTimeout(function() {
 | 
			
		||||
                self.fetch_data();
 | 
			
		||||
            }, this.interval*1000);
 | 
			
		||||
        else
 | 
			
		||||
            this.fetch_data();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Current selected sound (being played)
 | 
			
		||||
var CurrentSound = null;
 | 
			
		||||
 | 
			
		||||
var Sound = Vue.extend({
 | 
			
		||||
    template: '#template-sound',
 | 
			
		||||
    delimiters: ['[[', ']]'],
 | 
			
		||||
 | 
			
		||||
    data: function() {
 | 
			
		||||
        return {
 | 
			
		||||
            mounted: false,
 | 
			
		||||
            // sound state,
 | 
			
		||||
            state: State.Stop,
 | 
			
		||||
            // current position in playing sound
 | 
			
		||||
            position: 0,
 | 
			
		||||
            // estimated position when user mouse over progress bar
 | 
			
		||||
            seek_position: null,
 | 
			
		||||
            // url to the page related to the sound
 | 
			
		||||
            detail_url: '',
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        // sound can be seeked
 | 
			
		||||
        seekable: function() {
 | 
			
		||||
            // seekable: for the moment only when we have a podcast file
 | 
			
		||||
            // note: need mounted because $refs is not reactive
 | 
			
		||||
            return this.mounted && this.duration && this.$refs.audio.seekable;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // sound duration in seconds
 | 
			
		||||
        duration: function() {
 | 
			
		||||
            if(this.track.duration)
 | 
			
		||||
                return this.track.duration[0] * 3600 +
 | 
			
		||||
                       this.track.duration[1] * 60 +
 | 
			
		||||
                       this.track.duration[2];
 | 
			
		||||
            return null;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    props: {
 | 
			
		||||
        track: { type: Object, required: true },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.mounted = true;
 | 
			
		||||
        console.log(this.track, this.track.detail_url);
 | 
			
		||||
        this.detail_url = this.track.detail_url;
 | 
			
		||||
        this.storage_key = "sound." + this.track.sources[0];
 | 
			
		||||
 | 
			
		||||
        var pos = localStorage.getItem(this.storage_key)
 | 
			
		||||
        if(pos) try {
 | 
			
		||||
            // go back of 5 seconds
 | 
			
		||||
            pos = parseFloat(pos) - 5;
 | 
			
		||||
            if(pos > 0)
 | 
			
		||||
                this.$refs.audio.currentTime = pos;
 | 
			
		||||
        } catch (e) {}
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        //
 | 
			
		||||
        // Common methods
 | 
			
		||||
        //
 | 
			
		||||
        stop() {
 | 
			
		||||
            this.$refs.audio.pause();
 | 
			
		||||
            CurrentSound = null;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        play(reset = false) {
 | 
			
		||||
            if(CurrentSound && CurrentSound != this)
 | 
			
		||||
                CurrentSound.stop();
 | 
			
		||||
            CurrentSound = this;
 | 
			
		||||
            if(reset)
 | 
			
		||||
                this.$refs.audio.currentTime = 0;
 | 
			
		||||
            this.$refs.audio.play();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        play_stop() {
 | 
			
		||||
            if(this.state == State.Stop)
 | 
			
		||||
                this.play();
 | 
			
		||||
            else
 | 
			
		||||
                this.stop();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        add_to_playlist() {
 | 
			
		||||
            if(!DefaultPlaylist)
 | 
			
		||||
                return;
 | 
			
		||||
            var tracks = DefaultPlaylist.tracks;
 | 
			
		||||
            if(tracks.indexOf(this.track) == -1)
 | 
			
		||||
                DefaultPlaylist.tracks.push(this.track);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        remove() {
 | 
			
		||||
            this.stop();
 | 
			
		||||
            var tracks = this.$parent.tracks;
 | 
			
		||||
            var i = tracks.indexOf(this.track);
 | 
			
		||||
            if(i == -1)
 | 
			
		||||
                return;
 | 
			
		||||
            tracks.splice(i, 1);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // Events
 | 
			
		||||
        //
 | 
			
		||||
        timeUpdate() {
 | 
			
		||||
            this.position = this.$refs.audio.currentTime;
 | 
			
		||||
            if(this.state == State.Play)
 | 
			
		||||
                localStorage.setItem(
 | 
			
		||||
                    this.storage_key, this.$refs.audio.currentTime
 | 
			
		||||
                );
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        ended() {
 | 
			
		||||
            this.state = State.Stop;
 | 
			
		||||
            this.$refs.audio.currentTime = 0;
 | 
			
		||||
            localStorage.removeItem(this.storage_key);
 | 
			
		||||
            this.$emit('ended', this);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        _as_progress_time(event) {
 | 
			
		||||
            bounding = this.$refs.progress.getBoundingClientRect()
 | 
			
		||||
            offset = (event.clientX - bounding.left);
 | 
			
		||||
            return offset * this.$refs.audio.duration / bounding.width;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        progress_mouse_out(event) {
 | 
			
		||||
            this.seek_position = null;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        progress_mouse_move(event) {
 | 
			
		||||
            if(this.$refs.audio.duration == Infinity ||
 | 
			
		||||
                    isNaN(this.$refs.audio.duration))
 | 
			
		||||
               return;
 | 
			
		||||
            this.seek_position = this._as_progress_time(event);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        progress_clicked(event) {
 | 
			
		||||
            this.$refs.audio.currentTime = this._as_progress_time(event);
 | 
			
		||||
            this.play();
 | 
			
		||||
            event.stopImmediatePropagation();
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// User's default playlist
 | 
			
		||||
DefaultPlaylist = null;
 | 
			
		||||
 | 
			
		||||
var Playlist = Vue.extend({
 | 
			
		||||
    template: '#template-playlist',
 | 
			
		||||
    delimiters: ['[[', ']]'],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            // if true, use this playlist as user's default playlist
 | 
			
		||||
            default: false,
 | 
			
		||||
            // single mode enabled
 | 
			
		||||
            single_mode: false,
 | 
			
		||||
            // playlist can be modified by user
 | 
			
		||||
            modifiable: false,
 | 
			
		||||
            // if set, save items into localstorage using this root key
 | 
			
		||||
            storage_key: null,
 | 
			
		||||
            // sounds info
 | 
			
		||||
            tracks: [],
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted() {
 | 
			
		||||
        // set default
 | 
			
		||||
        if(this.default) {
 | 
			
		||||
            if(DefaultPlaylist)
 | 
			
		||||
                this.tracks = DefaultPlaylist.tracks;
 | 
			
		||||
            else
 | 
			
		||||
                DefaultPlaylist = this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // storage_key
 | 
			
		||||
        if(this.storage_key) {
 | 
			
		||||
            tracks = localStorage.getItem('playlist.' + this.storage_key);
 | 
			
		||||
            if(tracks)
 | 
			
		||||
                this.tracks = JSON.parse(tracks);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        sound_ended(sound) {
 | 
			
		||||
            // ensure sound is stopped (beforeDestroy())
 | 
			
		||||
            sound.stop();
 | 
			
		||||
 | 
			
		||||
            // next only when single mode
 | 
			
		||||
            if(this.single_mode)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var sounds = this.$refs.sounds;
 | 
			
		||||
            var id = sounds.findIndex(s => s == sound);
 | 
			
		||||
            if(id < 0 || id+1 >= sounds.length)
 | 
			
		||||
                return
 | 
			
		||||
            id++;
 | 
			
		||||
            sounds[id].play(true);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    watch: {
 | 
			
		||||
        tracks: {
 | 
			
		||||
            handler() {
 | 
			
		||||
                if(!this.storage_key)
 | 
			
		||||
                    return;
 | 
			
		||||
                localStorage.setItem('playlist.' + this.storage_key,
 | 
			
		||||
                                     JSON.stringify(this.tracks));
 | 
			
		||||
            },
 | 
			
		||||
            deep: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Vue.component('a-sound', Sound);
 | 
			
		||||
Vue.component('a-playlist', Playlist);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10798
									
								
								aircox_cms/static/lib/vue.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10798
									
								
								aircox_cms/static/lib/vue.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6
									
								
								aircox_cms/static/lib/vue.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								aircox_cms/static/lib/vue.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user