siomi / Next DCDNet HTML5 Player

// ==UserScript==
// @name         Next DCDNet HTML5 Player
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  replaces flash audio player with a standard HTML5 audio player
// @author       siomi
// @license      MIT; https://spdx.org/licenses/MIT.html
// @match        http://dcdnet.ru/albums/*
// @match        http://www.dcdnet.ru/albums/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.0/howler.min.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    /*Howl.prototype.changeSong = function(o) {
        var self = this;
        self.unload();
        self._duration = 0; // init duration
        self._sprite = {};// init sprite
        self._src = typeof o.src !== 'string' ? o.src : [o.src];
        self._format = typeof o.format !== 'string' ? o.format : [o.format];
        self.load(); // => update duration, sprite(var timeout)
    };*/
    let GM_addStyle = function(css) {
        const style = document.getElementById("GM_addStyleByDCD") || (function() {
            const style = document.createElement('style');
            style.type = 'text/css';
            style.id = "GM_addStyleByDCD";
            document.head.appendChild(style);
            return style;
        })();
        const sheet = style.sheet;
        sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
    }
    let getOffset = function(el) {
        const rect = el.getBoundingClientRect();
        return {
            left: rect.left + window.scrollX,
            top: rect.top + window.scrollY
        };
    }
    let p = document.getElementById("tracks");
    let d = document.createElement("div");
    d.classList.add("player_main");
    d.innerHTML = `<div class="player_main">
  <div class="player">
    <div class="buttons">
      <a class="play_repeat" id="repeat_btn" href="#"><svg class="btn active" viewBox="0 0 32 32"><path d="M29 16 C29 22 24 29 16 29 8 29 3 22 3 16 3 10 8 3 16 3 21 3 25 6 27 9 M20 10 L27 9 28 2"></path></svg></a>
      <a class="play_pause" id="play_btn" href="#"><svg class="btn" viewBox="0 0 32 32"><path d="M10 2 L10 30 24 16 Z"></path></svg></a>
      <a class="play_pause hidden" id="pause_btn" href="#"><svg class="btn" viewBox="0 0 32 32"><path d="M23 2 L23 30 M9 2 L9 30"></path></svg></a>
    </div>
    <div class="info">
       <div class="track_info">
         <span id="info_title"></span>
         <span id="info_duration"></span>
       </div>
       <div class="track_progress" id="info_progressbar">
         <div class="track_progress-value" id="info_progress"></div>
       </div>
    </div>
    <div class="volume">
      <span class="play_volume"><svg class="btn" viewBox="0 0 32 32"><path d="M20 16 C20 8 15 2 15 2 L8 10 2 10 2 22 8 22 15 30 C15 30 20 24 20 16 Z M21 2 C21 2 25 6 25 16 25 26 21 30 21 30 M27 4 C27 4 30 8 30 16 30 24 27 28 27 28"></path></svg></span>
      <div class="track_progress" id="info_volumebar">
         <div class="track_progress-value" id="info_volume"><span></span></div>
       </div>
    </div>
  </div>
  <div class="playlist">
    <div id="playlist_tracks">
    </div>
  </div>
</div>`;
    GM_addStyle('.player_main a,.player_main div{box-sizing:border-box;}');
    GM_addStyle('.player_main a,.player_main span{color:#fff;}');
    GM_addStyle('.player_main a{text-decoration:none;}');
    GM_addStyle('.player_main .hidden{display:none;}');
    GM_addStyle('.player_main{display:inline-block;width:100%;max-width:40em;background-color:#76989d}');
    GM_addStyle('.player_main .player,.player_main .playlist{display:flex;width:100%;max-width:40em}');
    GM_addStyle('.player_main .player{justify-content: space-between;}');
    GM_addStyle('.player_main .player .buttons,.player_main .playlist{display:flex}');
    GM_addStyle('.player_main .player .info{display:flex;padding:.6em;width:100%;max-width:28em;flex-direction:column}');
    GM_addStyle('.player_main .player .info .track_info{display:flex;justify-content:space-between;margin-bottom:.4em}');
    GM_addStyle('.player_main #info_title{white-space:nowrap;overflow:hidden}');
    GM_addStyle('.player_main .player .volume{display:flex;width:16em;align-items:center;padding:0 1em;flex:0 0 10em}');
    GM_addStyle('.player_main .playlist #playlist_tracks{background-color:#e0eaeb;list-style:none;display:flex;width:100%;flex-direction:column;counter-reset:section}');
    GM_addStyle('.player_main .playlist .track{display:flex;justify-content:space-around;width:100%;padding:.6em}');
    GM_addStyle('.player_main .track_info{display:flex;max-width:40em;width:100%;justify-content:space-between;align-items:left}');
    GM_addStyle('.player_main .track_info span.title{width:100%}');
    GM_addStyle('.player_main .player_main .track_info span{color:white}');
    GM_addStyle('.player_main .playlist .track_info span{color:black}');
    GM_addStyle('.player_main .playlist .track.active,.player_main .track:hover{background:rgba(255,255,255,.3);cursor:pointer}');
    GM_addStyle('.btn{display:inline-block;fill:none;margin:.6em .4em;font-size:1.2em;width:1.2em;stroke:currentColor;stroke-width:8%;overflow:visible}');
    GM_addStyle('.player_main .play_repeat .btn{stroke:rgba(255,255,255,.3)}');
    GM_addStyle('.player_main .play_repeat.active .btn{stroke:white}');
    GM_addStyle('.player_main .track_progress{background:rgba(255,255,255,.3);justify-content:flex-start;align-items:center;position:relative;display:flex;height:4px;width:100%}');
    GM_addStyle('.player_main .track_progress-value{background:#fff;height:4px;width:0%}');
    p.append(d);
    const tracks = document.querySelectorAll('#track_table tbody tr');
    let volume = 1.0;
    let repreat = true;
    let playlist = [];
    for(let i=0;i<tracks.length;i++){
        let track = tracks[i];
        let num = track.querySelector('td.track.number');
        let title = track.querySelector('td:nth-child(3)').innerText;
        let time = track.querySelector('td.time.number').innerText;
        let link = track.querySelector('object param[name=movie]').value.split('&')[1];
        playlist.push({
            file: 'http://dcdnet.ru'+link.substring(4, link.length)
            ,title: title
            ,length: time
        });
    }
    let tracks_el = document.getElementById("playlist_tracks");
    let title_el = document.getElementById("info_title");
    let duration_el = document.getElementById("info_duration");
    let progressbar_el = document.getElementById("info_progressbar");
    let progress_el = document.getElementById("info_progress");
    let volumebar_el = document.getElementById("info_volumebar");
    let volume_el = document.getElementById("info_volume");
    let play_btn = document.getElementById("play_btn");
    let pause_btn = document.getElementById("pause_btn");
    let repeat_btn = document.getElementById("repeat_btn");

    let Player = function (playlist) {
        this.playlist = playlist;
        this.index = 0;
        this.volume = 1.0;
        playlist.forEach(function(song) {
            let div = document.createElement("div");
            div.className = "track";
            div.innerHTML = '<div class="track_info"><span class="title">'+song.title+'</span><span class="time">'+song.length+'</span></div>';
            div.onclick = function () {
                player.skipTo(playlist.indexOf(song));
                let current = document.querySelector(".player_main .track.active")
                if(current !== null){
                    current.classList.remove("active");
                }
                this.classList.add("active");
                document.querySelector(".player_main .buttons a.hidden").classList.remove("hidden");
                play_btn.classList.add("hidden");
            };
            tracks_el.appendChild(div);
        });
    };

    Player.prototype = {
        play: function (index) {
            let self = this;
            let sound;
            index = typeof index === "number" ? index : self.index;
            let data = self.playlist[index];
            if (data.howl) {
                sound = data.howl;
            } else {
                sound = data.howl = new Howl({
                    src: [data.file],
                    html5: true,
                    onplay: function () {
                        duration_el.innerHTML = self.formatTime(Math.round(sound.duration()));
                        requestAnimationFrame(self.step.bind(self));
                    },
                    onload: function () {
                    },
                    onend: function () {
                        self.skip("next");
                    },
                    onpause: function () {
                    },
                    onstop: function () {

                    },
                    onseek: function() {
                        requestAnimationFrame(self.step.bind(self));
                    }
                });
            }
            sound.play();
            title_el.innerHTML = data.title;
            if (sound.state() === "loaded") {
            } else {
            }
            self.index = index;
        },
        pause: function () {
            var self = this;
            var sound = self.playlist[self.index].howl;
            sound.pause();
        },
        repeat_all: function(val) {
            var self = this;
            self.repeat = val;
        },
        skip: function (direction) {
            var self = this;
            var index = 0;
            if (direction === "prev") {
                index = self.index - 1;
                if (index < 0) {
                    index = self.playlist.length - 1;
                }
            } else {
                index = self.index + 1;
                if (self.repeat && index >= self.playlist.length) {
                    index = 0;
                }
                if (!self.repeat && index >= self.playlist.length) {
                    self.stop;
                } else {
                    self.skipTo(index);
                }
            }
        },
        skipTo: function (index) {
            var self = this;
            if (self.playlist[self.index].howl) {
                self.playlist[self.index].howl.stop();
            }
            progress_el.style.width = "0%";
            self.play(index);
        },
        vlm: function (val) {
            var self = this;
            Howler.volume(val);
            volume_el.style.width = (val * 100) + "%";
        },
        seek: function (per) {
            var self = this;
            var sound = self.playlist[self.index].howl;
            if (sound.playing()) {
                sound.seek(sound.duration() * per);
            }
        },
        step: function () {
            var self = this;
            var sound = self.playlist[self.index].howl;
            var seek = sound.seek() || 0;
            duration_el.innerHTML = self.formatTime(Math.round(seek));
            progress_el.style.width = ((seek / sound.duration()) * 100 || 0) + "%";
            if (sound.playing()) {
                requestAnimationFrame(self.step.bind(self));
            }
        },
        formatTime: function (secs) {
            var minutes = Math.floor(secs / 60) || 0;
            var seconds = secs - minutes * 60 || 0;
            return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
        },
    };

    let player = new Player(playlist);
    player.vlm(volume);
    player.skipTo(0);

    play_btn.addEventListener("click", function (e) {
        e.preventDefault();
        player.play();
        document.querySelectorAll(".player_main .buttons a.hidden").forEach(function(b){b.classList.remove("hidden")});
        this.classList.add("hidden");
    });
    pause_btn.addEventListener("click", function (e) {
        e.preventDefault();
        player.pause();
        document.querySelectorAll(".player_main .buttons a.hidden").forEach(function(b){b.classList.remove("hidden")});
        this.classList.add("hidden");
    });
    repeat_btn.addEventListener("click", function (e) {
        e.preventDefault();
        this.classList.toggle("active");
        player.repeat_all(this.classList.contains("active"));
    });
    let mousedown = false;
    progressbar_el.addEventListener("click", function (event) {
      player.seek((event.clientX - getOffset(this).left) / this.offsetWidth);
    });
    volumebar_el.addEventListener("mousemove", function (event) {
        if (mousedown) {
            player.vlm((event.clientX - getOffset(this).left) / this.offsetWidth);
        }
    });
    volumebar_el.addEventListener("mousedown", function (event) {
      mousedown = true;
    });
    volumebar_el.addEventListener("mouseup", function (event) {
      mousedown = false;
    });
    volumebar_el.addEventListener("mouseleave", function (event) {
      mousedown = false;
    });
    let tbl = p.querySelector("table");
    tbl.parentNode.removeChild(tbl);
})();