Du bist nicht angemeldet.

Drakor

Fortgeschrittener

  • »Drakor« ist der Autor dieses Themas

Beiträge: 204

Registrierungsdatum: 30.06.2011

Danksagungen: 105

  • Private Nachricht senden

1

09.07.2014, 07:51

Musikvideos aus den Bookmarks abspielen

Hi !

Wer kennt das nicht ? Man hat tausende Bookmarks irgendwelcher Musikvideos, die man damals gut fand und in die Bookmarks aufgenommen hat, doch hört man sie sich höchstens noch einmal an, wenn man sich an den Titel erinnert, oder sie durch Zufall in den Lesezeichen wiederfindet.
Da ich mir vor allem auch die Musikvideos anhören möchte, die ich mir zuvor gebookmarked habe, bin ich auf die Idee gekommen einen simplen Player zu schreiben, der aus meinen Lesezeichen die Youtube-(Musik)Videos herausfiltert und in zufälliger Reihenfolge abspielt. Dazu habe ich eine Chrome-Extension geschrieben, da diese einfachen Zugriff auf die Lesezeichen haben und ich mir dort somit nicht allzuviel Gedanken machen muss. Ferner öffnet diese Extension auf Knopfdruck eine Player-Seite in einem neuen Tab, die daraufhin sofort anfängt zufällige Musikvideos abzuspielen. Zum Abspielen der Videos selber bietet sich die Youtube-Player-API geradezu an, sodass ich auch weiß, wie ich das bewerkstellige. Ferner benutze ich fishbone.js um meinen Code in sinnvolle Module (Klassen) aufzuteilen.

Also los mit der Player-Seite, die die Extension öffnen soll:

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
    <head>
        <title>BookMarkPlay</title>
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>
        <!-- The <iframe> (and video player) will replace this <div> tag. -->
        <div id="container">
            <div id="player"></div>
        </div>

        <div id="controls">
            <button id="skip" type="button">Skip!</button>
            <button id="mode" type="button">Mode: Random</button>
        </div>

        <div id="history">
            <h2 id="historystart">Abgespielte Videos</h2>
        </div>

        <script src="fishbone.js"></script>
        <script src="player.js"></script>
    </body>
</html>


Wie man sieht habe ich etwas Komfort hinzugefügt, wie einen "Skip"-Button, mit dem man das aktuelle Video überspringen kann, was auch praktisch ist, da man leider nicht zwischen Musik- und etwa Tutorial-Videos oder kurzen Präsentationen unterscheiden kann und diese durch die zufällige Auswahl immer mal wieder auftreten. Außerdem hätte ich gerne zwei Modi, einen, in dem nach dem Ende eines Videos ein neues Video ausgesucht wird (zufällig) und einen Modus, in dem das aktuelle Video wiederholt wird. Zum Schluss werden alle bereits abgespielten Videos in einer Historie aufgelistet, sodass ich diese bei Bedarf anklicken und so nochmals anhören kann. Hierzu werden einfach a-Tag, die durch simple br-Zeilenumbrüche getrennt sind, angehängt. Man sollte dies in der Zukunft evtl. durch die passenderen ul-Elemente austauschen.

Das zum HTML zugehörige CSS sieht wie folgt aus (style.css):

Spoiler Spoiler

Cascading Style Sheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
body {
    margin: 0;
    background: url("bg.png") repeat; 
}

#controls, #container, #history {
    width: 800px;
    margin: 0 auto;
    text-align: center;
    margin-top: 20px;
}

#controls button {
    padding: 15px;
    padding-left: 15px;

    background: #ababab;
    color: #343434;

    font-size: 1.2em;
    font-weight: bold;

    border: 0px;
    border-radius: 5px;
}

#history {
    margin-top: 50px;
    text-align: left;
    line-height: 2em;
}

#history h2 {
    color: #fff;
    font-size: 2em;
    font-family: Arial;
}

#history a {
    font-family: Arial;
    font-size: 1em;
    font-weight: bold;
    color: #fff;
    text-decoration: none;
}

#history a:hover {
    text-decoration: underline;
}


Hier füge ich ein SubtlePatternsHintergrundbild ein und zentriere sämtliche Elemente und ordne sie passend an. Kommen wir zum JavaScript, Chrome bietet die Funktion chrome.bookmarks.getTree(callback), um die Bookmarks auszulesen. Somit müssen wir nur den an die Callback-Funktion übergebenen Bookmark-Baum traversieren und mittels Regex die Youtube-Lesezeichen herausfiltern.

Javascript-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Dies ist die callback-Funktion für getTree    
traverseBookmarks: function(bookmarks) {
        for (var i = 0; i < bookmarks.length; i++) {
            if (bookmarks[i].url && bookmarks[i].url.match(/youtube\./) && bookmarks[i].url.match(/\/watch/)) {
                var videoIdEx = /v=([^&#]*)&?/
                var result = videoIdEx.exec(bookmarks[i].url);
                if (result.length > 1) {
                    var newEntry = {};
                    newEntry.videoId = result[1];
                    newEntry.title = bookmarks[i].title;
                    //add to video list
                    this.videos.push(newEntry);
                }
            }

            //test if bookmark-node is itself a tree with children
            if (bookmarks[i].children) {
                this.traverseBookmarks(bookmarks[i].children);
            }
        }
    }


Dies ist außerdem eine Methode einer Player-Klasse, sodass uns nach dem traversieren eine Liste mit allen Youtube-VideoID's zur Verfügung steht. Man beachte, das hier auch sehr lange und auch evtl. Videos mit anderen Inhalten als Musik mit eingeschlossen sind. Leider gibt es keinen direkten Weg die Dauer eines Videos oder deren Inhalt über die normale Player-API zu prüfen. Fangen wir also nun an dort zufällige Videos auszuwählen:

Javascript-Quelltext

1
2
3
4
5
6
7
8
9
    startRandomSong: function() {

        var len = this.videos.length;
        var song = Math.floor((Math.random() * len) + 1);
        this.currentSong = song;


        this.ytplayer.loadVideo(this.videos[song].videoId);
    },

Um das Video zu laden benutze ich ein simples Wrapper Objekt für die Youtube-Player-API, da ich nicht direkt aus meiner Player-Klasse auf die API zugreifen will. loadVideo sorgt dafür, dass das entsprechende Video geladen UND automatisch abgespielt wird. Da ich aber keine Videos abspielen will, die lange Präsentationen / Tutorials oder z.b. auch Let's Plays enthalten, will ich nur Videos abspielen, die eine Dauer von maximal 10 Minuten haben. Dies sorgt zumindest bei meiner Lesezeichenstruktur dafür, dass der Anteil der tatsächlichen Musikvideos signifikant steigt. Allerdings werden so auch ganze Alben oder längere Musikvideos ausgeschlossen. Dieses ganze Verfahren ist somit noch sehr primitiv und eine sinnvolle Erweiterung in der Zukunft wäre es, Musikvideos z.b. in einen eigenen Bookmarkordner zu packen und nur diese dann beim Abspielen zu beachten, oder einen anderen Weg finden, die Musikvideos unserem Player zu markieren, sodass dieser sie von anderen Videos unterscheiden kann. Ferner wäre es auch cool, wenn wir in unserem Player bestimmte Musikvideos zu Playlisten zusammenfassen könnten, oder überhaupt auch Youtube-Playlisten auf irgendeine Art zu beachten.
Aber zurück die Dauer der Videos zu beschränken: Erst wenn ein Video geladen wird, läd Youtube auch die Meta-Daten, wie z.b. die Dauer. Daher baue ich uns eine kleine Prüfmethode, die immer am Start eines Videos aufgerufen wird und die langen Videos dann einfach überspringt:

Javascript-Quelltext

1
2
3
4
5
6
7
    checkDuration: function() {
        if (this.ytplayer.getDuration() > 10 * 60) {
            this.startRandomSong(); //skip current video
            //without putting the current one into the history etc.
            setTitle(this.videos[this.currentSong].title);
        }
    },

Hier sieht man auch, dass der Titel der Player-Seite immer an den Titel der Youtube-Seite angepasst wird, sodass man den Titel des aktuellen Song schon aus einem anderen Tab heraus sehen kann. Da ich aber auch zwischen den verschiedenen bereits abgesprochen Abspiel-Modis unterscheiden will, brauche ich noch eine Funktion, die, vom Modus abhängig, die passende Aktion auslöst:

Javascript-Quelltext

1
2
3
4
5
6
7
8
9
10
11
    playSong: function() {

        if (this.mode == 1) {
            this.ytplayer.playVideo(); // repeat video
        }
        else {
            addHistoryItem(this.videos[this.currentSong].title, this.currentSong); //add item to history
            this.startRandomSong();
            setTitle(this.videos[this.currentSong].title);
        }
    },

Dies ist auch die Funktion, die aufgerufen wird, sobald das aktuelle Video zu Ende ist, sodass sofort ein neues Video ausgesucht bzw. das Video noch einmal wiederholt werden kann. Außerdem will ich auch Videos aus der Historie der bereits abgespielten Lieder abspielen, sodass wir eine Funktion benötigen, die ein bestimmtes Video abspielen kann:

Javascript-Quelltext

1
2
3
4
5
6
7
    playSpecificSong: function(index) {
        addHistoryItem(this.videos[this.currentSong].title, this.currentSong);
        this.currentSong = index;
        setTitle(this.videos[index].title);

        this.ytplayer.loadVideo(this.videos[index].videoId);
    },

Anzumerken ist, dass der Array-Index des Videos, den wir benötigen, um das Video erneut abzuspielen, als Attibut für das a-Element abgespeichert wird.

Javascript-Quelltext

1
2
3
4
5
    var entry = document.createElement("a");
    entry.href = "#";
    entry.song = index;
    entry.innerHTML = title;
    entry.addEventListener("click", playHistorySong);

Damit hätten wir bereits einen soweit funktionierenden Player beisammen. Jetzt fehlt noch eine Main-Methode, die die Objekte baut und die Callbacks der Player Klasse für die verschiedenen Events der Youtube-API registriert. Als Main-Methode habe ich hier die Callback-Methode gewählt, die aufgerufen wird, sobald die Youtube-Player-API selber vollständig geladen und initialisiert wurde:

Javascript-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// is called after the youtube API finishes loading
function onYouTubeIframeAPIReady() {
    ytplayer = new YoutubePlayer("player");
    bookmarkplay = new BookmarkPlay(ytplayer);

    var skipButton = document.getElementById("skip");
    var modeButton = document.getElementById("mode");
    skipButton.addEventListener("click", skipVideo, false);
    modeButton.addEventListener("click", toggleMode, false);
    
    ytplayer.on("onReady", startLoop);
    bookmarkplay.importBookmarks();
    ytplayer.loadPlayer();
}


Die relevanten Callbacks werden im Konstruktor der Player-Klasse registiert:

Javascript-Quelltext

1
2
3
4
5
6
7
8
9
10
var BookmarkPlay = Model({
    init: function(ytplayer) {
        this.ytplayer = ytplayer;
        this.videos = [];
        this.mode = 0; //random mode
        this.currentSong = -1;

        this.ytplayer.on("videoEnded", function() {this.playSong();}.bind(this));
        this.ytplayer.on("videoPlaying", function() {this.checkDuration();}.bind(this));
    }, ...


Zu allerletzt müssen wir unserem Chrome-Browser nur noch Bescheid sagen, dass von uns erstellen Dateien zusammen eine Extension bilden und außerdem müssen wir die Player-Seite bei Klick auf das Extension-Icon öffnen:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//manifest.json
{
  "manifest_version": 2,

  "name": "Bookmark-Player",
  "description": "Plays random youtube songs from your bookmark list.",
  "version": "1.0",
  "content_security_policy": "script-src 'self' 'unsafe-eval' https://www.youtube.com/iframe_api https://s.ytimg.com/yts/jsbin/www-widgetapi-vfldqBTcy.js https://s.ytimg.com/yt/jsbin/www-widgetapi-vfl9Ni0ki.js; object-src 'self'",
  "background": {"page": "background.html"},

  "permissions": [
    "bookmarks",
    "https://youtube.com/"
  ],
  "browser_action": {
    "default_icon": "icon.png"
  },

  "web_accessible_resources": [
      "player.html",
      "player.js"
  ]
}

Sowie einige Background-Dateien (die den Klick-Handler der Browser-Action verwalten):

Spoiler Spoiler

HTML

1
2
3
4
5
6
7
8
9
10
//background.html
<html>
<head>
    <script src="background.js"></script>
</head

<body>
</body>

</html>

Javascript-Quelltext

1
2
3
4
5
6
//background.js
chrome.browserAction.onClicked.addListener(function(activeTab)
{
    var newURL = chrome.extension.getURL("player.html");
    chrome.tabs.create({ url: newURL });
})



Das wärs, damit kann ich endlich wieder in den Tiefen meiner Lesezeichen nach Musik stöbern. Ich würde mich über Feedback und auch Verbesserungsvorschlägen und Hinweisen zum Code sehr freuen und ich hoffe ich konnte inspirieren, selbst einmal mit der Youtube-Player-API oder Chrome-Extensions herumzubasteln.

Gruß
Drakor
»Drakor« hat folgende Dateien angehängt:
  • bookmarkplay.crx (14,5 kB - 68 mal heruntergeladen - zuletzt: 15.11.2017, 02:46)
  • bookmarkplay.zip (14,62 kB - 70 mal heruntergeladen - zuletzt: 15.11.2017, 02:46)

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Drakor« (09.07.2014, 07:59)


Es haben sich bereits 2 registrierte Benutzer bedankt.

Benutzer, die sich für diesen Beitrag bedankt haben:

Erik (09.07.2014), Patrick (15.07.2014)

DanielBocksteger

unregistriert

2

09.07.2014, 10:02

Danke Drakor, für den Beitrag!

Zwar abe ich selber 0,0% Musik in meinen Lesezeichen, kann mir allerdings gut vorstellen, dass du trotzdem dem ein oder anderen helfen oder auch einfach das Interesse an den beiden Technologien wecken konntest :-)

Es hat sich bereits 1 registrierter Benutzer bedankt.

Benutzer, die sich für diesen Beitrag bedankt haben:

Drakor (09.07.2014)

Drakor

Fortgeschrittener

  • »Drakor« ist der Autor dieses Themas

Beiträge: 204

Registrierungsdatum: 30.06.2011

Danksagungen: 105

  • Private Nachricht senden

3

09.07.2014, 13:17

Mir sind zwei Bugs aufgefallen, die ich nachher beheben werde, hier aber kurz zeigen möchte.

1. Es wird nicht geprüft, ob das Array mit den Youtube-Videos auch tatsächlich Videos enthält, könnte also z.b. wie bei Daniel komplett leer sein.

2. Die Zeile:

Javascript-Quelltext

1
var song = Math.floor((Math.random() * len) + 1);

erzeugt definitiv invalide Array-Indizes! Gerade wenn man sich len = 0 vorstellt wird das sehr gut deutlich, das "+1" hat also dort nichts zu suchen. (Auch wird es schwierig, das erste Element aus dem Array zu bekommen)

Gruß
Drakor