forked from web-scrobbler/web-scrobbler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
youtube.js
348 lines (275 loc) · 11.6 KB
/
youtube.js
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
// State for event handlers
var state = 'init';
// Used only to remember last song title
var clipTitle = '';
// Options
var options = {};
$(document).ready(function() {
chrome.extension.sendRequest({type: 'getOptions'}, function(response) {
options = response.value;
init();
});
});
function init() {
// bindings to trigger song recognition on various (classic, profile) pages
if (document.location.toString().indexOf('/watch#!v=') > -1) {
// === AJAX page load =======================================================
// Hook up for changes in header (Loading... -> Artist - Title) on CLASSIC page
$('#watch-pagetop-section').bind('DOMSubtreeModified', function(e) {
// simple FSM (not only) to prevent multiple calls of updateNowPlaying()
// init ----> loading
if (state == 'init' && $('#watch-loading').length > 0) {
state = 'loading';
return;
}
// watching ----> loading
if (state == 'watching' &&
$('#watch-headline-title > span[title][id!=chrome-scrobbler-status]').length>0 &&
$('#watch-headline-title > span[title][id!=chrome-scrobbler-status]').attr('title') != clipTitle
) {
// fake loading state to match the next condition and reload the clip info
state = 'loading';
}
// loading --[updateNowPlaying(), set clipTitle]--> watching
if (state == 'loading' && $('#watch-headline-title').length > 0) {
state = 'watching';
clipTitle = $('#watch-headline-title > span[title][id!=chrome-scrobbler-status]').attr('title');
updateNowPlaying();
return;
}
});
} else if ( $('#playnav-player').length > 0 ) {
// Hook up for changes in title on users profile page
$('#playnav-video-details').bind('DOMSubtreeModified', function(e) {
if ($('#playnav-curvideo-title > span[id!=chrome-scrobbler-status]').length > 0
|| $('#playnav-curvideo-title > a').length > 0) {
// just check changes in the song title
var titleEl = $('#playnav-curvideo-title > span[id!=chrome-scrobbler-status]');
if (titleEl.length == 0)
titleEl = $('#playnav-curvideo-title > a'); // newer version
if (clipTitle != titleEl.text()) {
updateNowPlaying();
clipTitle = titleEl.text();
}
}
});
// fire the DOMSubtreeModified event on first pageload (the binding above is executed after full DOM load)
$('#playnav-video-details').trigger('DOMSubtreeModified');
} else {
// === regular page load ====================================================
/*
// inject stats code
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-16968457-1']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = 'https://ssl.google-analytics.com/ga.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
})();
*/
// start scrobbler
updateNowPlaying();
}
// bind page unload function to discard current "now listening"
$(window).unload(function() {
// reset the background scrobbler song data
chrome.extension.sendRequest({type: 'reset'});
return true;
});
}
/**
* Trim whitespaces from both endings of the string
*/
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
/**
* Find first occurence of possible separator in given string
* and return separator's position and size in chars or null
*/
function findSeparator(str) {
// care - minus vs hyphen
var separators = [' - ', ' – ', '-', '–', ':'];
for (i in separators) {
var sep = separators[i];
var index = str.indexOf(sep);
if (index > -1)
return { index: index, length: sep.length };
}
return null;
}
/**
* Parse given string into artist and track, assume common order Art - Ttl
* @return {artist, track}
*/
function parseInfo(artistTitle) {
var artist = '';
var track = '';
var separator = findSeparator(artistTitle);
if (separator == null)
return { artist: '', track: '' };
artist = artistTitle.substr(0, separator.index);
track = artistTitle.substr(separator.index + separator.length);
return cleanArtistTrack(artist, track);
}
/**
* Clean non-informative garbage from title
*/
function cleanArtistTrack(artist, track) {
// Do some cleanup
artist = artist.replace(/^\s+|\s+$/g,'');
track = track.replace(/^\s+|\s+$/g,'');
// Strip crap
track = track.replace(/\s*\*+\s?\S+\s?\*+$/, ''); // **NEW**
track = track.replace(/\s*\[[^\]]+\]$/, ''); // [whatever]
track = track.replace(/\s*\([^\)]*version\)$/i, ''); // (whatever version)
track = track.replace(/\s*\.(avi|wmv|mpg|mpeg|flv)$/i, ''); // video extensions
track = track.replace(/\s*(of+icial\s*)?(music\s*)?video/i, ''); // (official)? (music)? video
track = track.replace(/\s*(ALBUM TRACK\s*)?(album track\s*)/i, ''); // (ALBUM TRACK)
track = track.replace(/\s*\(\s*of+icial\s*\)/i, ''); // (official)
track = track.replace(/\s*\(\s*[0-9]{4}\s*\)/i, ''); // (1999)
track = track.replace(/\s+\(\s*(HD|HQ)\s*\)$/, ''); // HD (HQ)
track = track.replace(/\s+(HD|HQ)\s*$/, ''); // HD (HQ)
track = track.replace(/\s*video\s*clip/i, ''); // video clip
track = track.replace(/\s+\(?live\)?$/i, ''); // live
track = track.replace(/\(\s*\)/, ''); // Leftovers after e.g. (official video)
track = track.replace(/^(|.*\s)"(.*)"(\s.*|)$/, '$2'); // Artist - The new "Track title" featuring someone
track = track.replace(/^(|.*\s)'(.*)'(\s.*|)$/, '$2'); // 'Track title'
track = track.replace(/^[\/\s,:;~-\s"]+/, ''); // trim starting white chars and dash
track = track.replace(/[\/\s,:;~-\s"\s!]+$/, ''); // trim trailing white chars and dash
//" and ! added because some track names end as {"Some Track" Official Music Video!} and it becomes {"Some Track"!} example: http://www.youtube.com/watch?v=xj_mHi7zeRQ
return {artist: artist, track: track};
}
/**
* Display message in front of clip title,
* call without parameter to clean the message.
*/
function displayMsg(msg) {
// consider options
if (getOption('useYTInpage') != 1)
return;
$('#chrome-scrobbler-status').remove();
if (msg) {
// regular page
if ($('#watch-headline-title > span[title][id!=chrome-scrobbler-status]').length>0)
$('<span id="chrome-scrobbler-status" title="">'+msg+'</span>').insertBefore($('#watch-headline-title > span[title][id!=chrome-scrobbler-status]'));
// user profile
if ($('#playnav-curvideo-title > span[id!=chrome-scrobbler-status]').length>0)
$('<span id="chrome-scrobbler-status" title="">'+msg+'</span>').insertBefore($('#playnav-curvideo-title > span[id!=chrome-scrobbler-status]'));
}
}
/**
* Called every time the player reloads
*/
function updateNowPlaying() {
// get the video ID
var videoID = document.URL.match(/v[^a-z]([a-zA-Z0-9\-_]{11})/);
if (videoID && videoID.length > 0)
videoID = videoID[1];
else
videoID = null;
// videoID from title at profile page
if ($('#playnav-curvideo-title > span[id!=chrome-scrobbler-status]').length > 0) {
videoID = $('#playnav-curvideo-title > span[id!=chrome-scrobbler-status]').attr('onclick').toString().match(/\?v=(.{11})/);
if (videoID && videoID.length > 0)
videoID = videoID[1];
}
// videoID from title at profile page - newer version
if ($('#playnav-curvideo-title > a').length > 0) {
videoID = $('#playnav-curvideo-title > a').attr('href').toString().match(/\?v=(.{11})/);
if (videoID && videoID.length > 0)
videoID = videoID[1];
}
// something changed?
if (!videoID) {
console.log('If there is a YouTube player on this page, it has not been recognized. Please fill in an issue at GitHub');
//alert('YouTube has probably changed its code. Please get newer version of the Last.fm Scrobbler extension');
return;
}
// http://code.google.com/intl/cs/apis/youtube/2.0/developers_guide_protocol_video_entries.html
var googleURL = "http://gdata.youtube.com/feeds/api/videos/" + videoID + "?alt=json";
// clear the message
displayMsg();
// Get clip info from youtube api
chrome.extension.sendRequest({type: "xhr", url: googleURL}, function(response) {
var info = JSON.parse(response.text);
var parsedInfo = parseInfo(info.entry.title.$t);
var artist = null;
var track = null;
// Use the #eow-title #watch-headline-show-title if available
var track_dom = $('#eow-title').clone();
var artist_dom = $('#watch-headline-show-title', track_dom);
// there is a hyperlink of artist in title
if (artist_dom.length) {
var wholeTitleText = trim( track_dom.text() );
artist = artist_dom.text();
var artistIndex = wholeTitleText.indexOf(artist);
var separator = findSeparator(wholeTitleText);
// no separator found, parseInfo would fail too
if (separator == null) {
parsedInfo = { artist: '', track: '' };
}
// separator AFTER artist, common order, cut after separator
else if (separator.index > artistIndex) {
track = wholeTitleText.substr(separator.index + separator.length);
}
// separator BEFORE artist, reversed order, cut before separator
else {
track = wholeTitleText.substr(0, separator.index);
}
parsedInfo = cleanArtistTrack(artist, track);
}
// just a plain text title
else if (parsedInfo['artist'] == '') {
parsedInfo = parseInfo(track_dom.text());
}
artist = parsedInfo['artist'];
track = parsedInfo['track'];
// get the duration from the YT API response
var duration = '';
if (info.entry.media$group.media$content != undefined)
duration = info.entry.media$group.media$content[0].duration;
else if (info.entry.media$group.yt$duration.seconds != undefined)
duration = info.entry.media$group.yt$duration.seconds;
// Validate given artist and track (even for empty strings)
chrome.extension.sendRequest({type: 'validate', artist: artist, track: track}, function(response) {
// on success send nowPlaying song
if (response != false) {
var song = response; // contains valid artist/track now
// substitute the original duration with the duration of the video
chrome.extension.sendRequest({type: 'nowPlaying', artist: song.artist, track: song.track, duration: duration});
}
// on failure send nowPlaying 'unknown song'
else {
chrome.extension.sendRequest({type: 'nowPlaying', duration: duration});
displayMsg('Not recognized');
}
});
});
}
/**
* Gets an value from extension's localStorage (preloaded to 'options' object because of a bug 54257)
*/
function getOption(key) {
return options[key];
}
/**
* Listen for requests from scrobbler.js
*/
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
switch(request.type) {
// called after track has been successfully marked as 'now playing' at the server
case 'nowPlayingOK':
displayMsg('Scrobbling');
break;
// not used yet
case 'submitOK':
break;
// not used yet
case 'submitFAIL':
//alert('submit fail');
break;
}
}
);