(Originally posted July 22, 2013)
Last time we added long polling functionality so the queue is updated continuously across users. In this post, we’re going to add more information to the models so they look better to the user, and allow us to keep the information better.
The videos we play are identified on youtube by a key, and all of the urls have them, so we want to use the parse that we have from last time to snag the key and store it in the database. We also want to store the title of the video, because we want users to be able to identify what’s next. To do this, it requires a change in the model. After adding the desired fields, models.py should look like
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from youtubeparty import app from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) class YTUrl(db.Model): id = db.Column(db.Integer, primary_key = True ) url = db.Column(db.String( 80 )) title = db.Column(db.String( 40 )) video_key = db.Column(db.String( 20 )) def __init__( self , url, title, video_key): self .url = url self .title = title self .video_key = video_key def __repr__( self ): return self .title db.create_all() |
Now we need to change the view where the urls are uploaded to gather and store this additional information. Youtube has a api where we are able to request a bunch of the information about a video, without having to screenscrape the actual website. This url, if given the video’s identifying key, will return json that we can then parse.
1
2
3
4
5
6
7
8
9
10
|
@app .route( '/update/' ) def update(): queue = db.session.query(YTUrl) queue_list = [] for q in queue. all (): info = {} info[ 'title' ] = q.title info[ 'key' ] = q.video_key queue_list.append(info) return jsonify(queue = queue_list) |
If you visit /update, you should see the new json response with the extra data. With this new info, we have to change the javascript to display the titles, instead of the urls, as well as have queues to play the videos. Only after we have played and unplayed lists will we want to think about actually playing the videos as each of the functionalities are separate. The following code should be wrapped in the jquery document ready function.
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
|
var played_videos = []; //so we know we played the videos, var unplayed_videos = []; //which ones we haven't played function update(){ $.ajax({ url:'/update/ ', dataType: "json", success: function(response) { $(' .queue-list ').children().remove(); for (q in response[' queue ']) { var title = response[' queue '][q][' title ']; var key = response[' queue '][q][' key ']; //we need to see if we' ve already played the video var item; if ($.inArray(key, played_videos) > -1) { //add extra class of played video item = $( "<div>" , {class: "row vid-played" , text:title, key:key}); } else { //if we get here, we need to check if we want to add it to the unplayed list item = $( "<div>" , {class: "row" , text:title, key:key}); if ($.inArray(key, unplayed_videos) <= -1) { //add extra class of played video unplayed_videos.push(key); //list of videos to queue } } $( '.queue-list' ).append(item); }; }, }); } update(); //call now to get the thing to load right away setInterval(update,8000); |
The extra logic here is checking to see the state of the video. If it’s been played, we add the extra class so we can do some styling. If it hasn’t, we also want to check to see if we’ve put it in the unplayed queue yet, so we don’t keep throwing in values.
With the queues correct, we now need to deal with the youtube api. Luckily, their documentation is very well done and adding this functionality is almost copy / paste. The reference for this is found at https://developers.google.com/youtube/iframe_api_reference. The first step is to add an element where the player will go. Depending on your design, the only important thing is that there is a div with id yt-player, or anything that you want to name it.
1
2
3
4
5
6
7
8
|
< div class = "row" > < div class = "large-4 columns" > </ div > < div class = "large-4 columns" > < div id = "yt-player" > </ div > </ div > </ div > |
In your javascript file, we now want to add the following code, which is from the api website mentioned above.
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
|
var tag = document.createElement( 'script' ); var firstScriptTag = document.getElementsByTagName( 'script' )[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); var player; onYouTubeIframeAPIReady = function () { player = new YT.Player( 'yt-player' , { height: '250' , width: '400' , videoId: null , events: { 'onReady' : onPlayerReady, 'onStateChange' : onStateChange, } }); } function onPlayerReady(event) { player.loadVideoById(unplayed_videos[0]); } function onStateChange(event) { if (event.data == 0) { //ended //load the next song played_videos.push(unplayed_videos.shift()); player.loadVideoById(unplayed_videos[0]); } } |
The only difference from the website is that we added an onStateChange event to the player. This allows us to dynamically set the next video based on the queues we set up from the update function above. Note that all this should also go in the jquery document ready function. Changing onYouTubeIframeAPIReady function to the syntax above allows this to work.
After this, you should be able to load a page and see the queue all pretty with working sync from a different tab. The one issue is that if we want to add another link from the tab, the whole queue starts over. One standard way to work around this is to intercept the submit, and use ajax to post the url. But since we want to add a search functionality, we’ll pass on that, and next time, see how to do the search and use ajax there.
As always, if something doesn’t make sense, shoot me an email with the question / comment and I’ll be more than happy to reply.