1
/* Any frequently used shortcuts, strings and constants */
3
/* encourage jslint not to worry about global soundManager */
4
/*global soundManager: false */
7
/* PreviewPlayer class constructor */
8
function PreviewPlayer() {
9
PreviewPlayer.superclass.constructor.apply(this, arguments);
12
PreviewPlayer.NAME = "previewPlayer";
14
/** hash of static CSS class names
16
* @property CSS_CLASSES
21
PreviewPlayer.CSS_CLASSES = {
22
previewLink: ".preview",
23
previewCtrl: ".preview .ctrl",
24
previewText: ".preview .acchide",
25
previewPlayClass: "preview-play",
26
previewPauseClass: "preview-pause",
28
trackNum: ".track-num",
29
trackNumContainer: ".num",
33
progressBar: ".progressbar",
34
progressPlay: ".play",
38
PreviewPlayer.ATTRS = {
40
* width of row as apposed to width of contentBox
41
* TODO consider using default 'width' ATTR
44
value: "" // default value of player width
51
PreviewPlayer.HTML_PARSER = {
52
rowWidth: function(srcNode) {
53
return srcNode.get('offsetWidth');
57
Y.extend(PreviewPlayer, Y.Widget, {
59
CONTENT_TEMPLATE: null,
61
initializer: function() {
62
// Use a cache object to stash our song objects for re-use
63
// really just a way to enforce a limit on how many items
65
this.cache = new Y.Cache({max:10, uniqueKeys: true});
68
renderUI : function() {
69
var vuTemplate = Y.one("#tpl-vu-meter"),
70
progressbarTemplate = Y.one("#tpl-progressbar");
72
this.set("vuMeter", Y.Node.create(vuTemplate.getContent()));
73
this.set("progress", Y.Node.create(progressbarTemplate.getContent()));
77
var srcNode = this.get("srcNode");
78
srcNode.delegate("click", this.handleClick, PreviewPlayer.CSS_CLASSES.previewLink, this);
81
Y.on("windowresize", Y.bind(this.handleResize, this));
83
this.after('rowWidthChange', this.afterTrWidthChange);
85
// perform Unity integration if required.
86
Y.Unity.onInit(Y.bind(this.bindUnity, this));
89
// Beyond this point is the PreviewPlayer specific application and rendering logic
91
handleClick: function(e) {
93
var target = e.currentTarget,
94
currentSound = this.get("currentSound"),
95
sound = this.getOrCreateSound(target);
97
if (sound === currentSound) {
110
* update the rowWidth attribute when a resize event is fired
112
handleResize: function() {
113
var srcNode = this.get("srcNode");
114
return this.set('rowWidth', srcNode.one('li').get('offsetWidth'));
117
* handle change to rowWidth attr
119
afterTrWidthChange: function() {
120
var bar = this.get("progressBar");
122
bar.setStyle("width", this.get("rowWidth")+"px");
125
getTrackId: function(node) {
126
return "u1ms" + node.getAttribute("data-u1ms-trackid");
128
getTrackUrl: function(node){
129
return node.getAttribute("href");
131
getOrCreateSound: function(node) {
132
var id = this.getTrackId(node),
133
url = this.getTrackUrl(node),
137
cachedSound = this.cache.retrieve(id);
138
if (cachedSound === null) {
139
sound = sm.createSound({
142
onpause: this.soundPause,
143
onplay: this.soundPlay,
144
onresume: this.soundPlay,
145
onstop: this.soundStop,
146
whileloading: this.soundWhileLoading,
147
whileplaying: this.soundWhilePlaying,
148
onload: this.soundOnload,
149
onfinish: this.soundFinish,
163
this.cache.add(id, sound);
165
sound = cachedSound.response;
171
* Preload the next track in the list
173
getFirstNode: function() {
174
var srcNode = this.get("srcNode");
175
return srcNode.one(PreviewPlayer.CSS_CLASSES.previewLink);
178
getNextNode: function(currentNode) {
182
currentRow = currentNode.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
183
nextRow = currentRow.next(PreviewPlayer.CSS_CLASSES.rowSel);
186
return nextRow.one(PreviewPlayer.CSS_CLASSES.previewLink);
191
getPrevNode: function(currentNode) {
195
currentRow = currentNode.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
196
prevRow = currentRow.previous(PreviewPlayer.CSS_CLASSES.rowSel);
199
return prevRow.one(PreviewPlayer.CSS_CLASSES.previewLink);
204
setActiveRow: function(currentNode, sound) {
205
var currentRow = currentNode.ancestor(PreviewPlayer.CSS_CLASSES.rowSel),
206
previewText, previewCtrl, vu, vuLeft, vuRight, progress;
208
currentRow.addClass("active");
209
previewText = currentRow.one(PreviewPlayer.CSS_CLASSES.previewText);
211
previewText.setContent("Pause");
213
previewCtrl = currentRow.one(PreviewPlayer.CSS_CLASSES.previewCtrl);
215
previewCtrl.removeClass(PreviewPlayer.CSS_CLASSES.previewPlayClass);
216
previewCtrl.addClass(PreviewPlayer.CSS_CLASSES.previewPauseClass);
220
if (!this.get("progressBar")) {
221
progress = this.get("progress");
223
this.set("progressBar", progress.one(PreviewPlayer.CSS_CLASSES.progressBar));
224
this.set("playMeter", progress.one(PreviewPlayer.CSS_CLASSES.progressPlay));
225
this.set("loadMeter", progress.one(PreviewPlayer.CSS_CLASSES.progressLoad));
227
currentRow.appendChild(progress);
231
currentRow.appendChild('<span class="vu vu-dummy"></span>');
233
vu = this.get("vuMeter");
235
vuLeft = vu.one(PreviewPlayer.CSS_CLASSES.vuLeft);
236
vuRight = vu.one(PreviewPlayer.CSS_CLASSES.vuRight);
237
if (vuLeft && vuRight) {
238
vuLeft.setStyle("height", 0);
239
vuRight.setStyle("height", 0);
241
currentRow.appendChild(vu);
245
clearActiveRow: function() {
246
var srcNode = this.get("srcNode"),
247
currentRow = srcNode.one(".active"),
248
currentNode, previewText, previewCtrl, vu, progress, pm, lm;
251
currentNode = currentRow.one(PreviewPlayer.CSS_CLASSES.previewLink);
253
currentRow.removeClass("active");
255
// set meter to zero early so it doesn't appear
256
// in the next track as the old value
257
pm = this.get("playMeter");
259
pm.setStyle('width', '0');
262
lm = this.get("loadMeter");
264
lm.setStyle('width', '0');
267
previewText = currentRow.one(PreviewPlayer.CSS_CLASSES.previewText);
269
previewText.setContent("Play");
271
previewCtrl = currentRow.one(PreviewPlayer.CSS_CLASSES.previewCtrl);
273
previewCtrl.removeClass(PreviewPlayer.CSS_CLASSES.previewPauseClass);
274
previewCtrl.addClass(PreviewPlayer.CSS_CLASSES.previewPlayClass);
276
vu = currentRow.one(PreviewPlayer.CSS_CLASSES.vu);
280
progress = currentRow.one(PreviewPlayer.CSS_CLASSES.progressBar);
286
showThrobber: function (sound) {
287
var currentRow = sound.u1msData.node.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
288
currentRow.addClass('loading');
289
sound.u1msData.throbbing = true;
291
removeThrobber: function (sound) {
295
// if we're not throbbing, do not do anything
296
if(sound.u1msData.throbbing === false) {
300
currentRow = sound.u1msData.node.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
301
currentRow.removeClass('loading');
303
// if theres a throbber, remove it
304
throbber = currentRow.one('.throbber');
309
sound.u1msData.throbbing = false;
311
soundPlay: function() {
312
// context is that of the sound object
313
var that = this.u1msData.widget,
314
currentNode = this.u1msData.node,
318
that.set("currentSound", this);
319
that.clearActiveRow();
321
// song not loaded, show throbber
322
if(this.readyState !== 3) {
323
that.showThrobber(this);
326
that.setActiveRow(currentNode, this);
328
mp = Y.Unity.Unity.MediaPlayer;
329
mp.setPlaybackState(mp.PlaybackState.PLAYING);
331
title: currentNode.getAttribute("data-u1ms-title"),
332
artist: currentNode.getAttribute("data-u1ms-artist"),
333
album: currentNode.getAttribute("data-u1ms-release"),
334
artLocation: currentNode.getAttribute("data-u1ms-coverart")
336
mp.setTrack(trackInfo);
337
// And show a notification
338
Y.Unity.Unity.Notification.showNotification(
340
"by " + trackInfo.artist + " from " + trackInfo.album,
341
trackInfo.artLocation);
344
soundResume: function() {
345
// context is that of the sound object
346
var that = this.u1msData.widget,
347
currentNode = this.u1msData.node;
348
that.clearActiveRow();
349
that.setActiveRow(currentNode, this);
351
soundPause: function() {
352
// context is that of the sound object
353
var that = this.u1msData.widget,
356
that.clearActiveRow();
358
mp = Y.Unity.Unity.MediaPlayer;
359
mp.setPlaybackState(mp.PlaybackState.PAUSED);
362
soundStop: function() {
363
// context is that of the sound object
364
var that = this.u1msData.widget,
367
that.clearActiveRow();
369
mp = Y.Unity.Unity.MediaPlayer;
370
mp.setPlaybackState(mp.PlaybackState.PAUSED);
373
soundFinish: function() {
374
// context is that of the sound object
375
var that = this.u1msData.widget,
376
currentNode = this.u1msData.node,
377
next = that.getNextNode(currentNode),
380
nextSound = that.getOrCreateSound(next);
383
that.clearActiveRow();
386
soundWhileLoading: function() {
387
var that = this.u1msData.widget;
388
that.updateLoadMeter(this.bytesLoaded, this.bytesTotal);
390
soundWhilePlaying: function() {
391
// context is that of the sound object
392
var that = this.u1msData.widget;
394
// in html5 mode this is called prematurely
395
// so skip that fired event altogheter
396
if(this.isHTML5 && this.position === 0) {
400
// throbber should never exist while playing
401
that.removeThrobber(this);
403
if (sm.flashVersion >= 9 && this.instanceOptions.usePeakData) {
404
that.updatePeakData(this);
406
that.updatePlayMeter(this.position, this.durationEstimate);
408
updatePeakData: function(sound) {
409
var vu = sound.u1msData.vu;
412
vu.vuLeft.setStyle('height', Math.ceil(17*sound.peakData.left)+'px');
413
vu.vuRight.setStyle('height', Math.ceil(17*sound.peakData.right)+'px');
416
vu.node = sound.u1msData.node.ancestor('li').one(PreviewPlayer.CSS_CLASSES.vu);
417
vu.vuLeft = vu.node.one(PreviewPlayer.CSS_CLASSES.vuLeft);
418
vu.vuRight = vu.node.one(PreviewPlayer.CSS_CLASSES.vuRight);
423
* update the display of the load meter
424
* set width to 100% when we're done, so it adjusts to resize events
425
* @param current {Number} bytes loaded
426
* @param total {Number} total bytes to load
428
updateLoadMeter: function(current, total) {
429
var width = this.get("rowWidth"),
432
ratio = (current/total);
437
width = Math.floor(ratio * width) + 'px';
439
this.get("loadMeter").setStyle("width", width);
442
* update the display of the play meter
443
* @param current {Number} bytes played
444
* @param total {Number} total bytes to play
446
updatePlayMeter: function(current, total) {
447
var width = this.get('rowWidth');
449
width *= (current/total);
450
this.get('playMeter').setStyle('width', Math.floor(width) +'px');
453
// Preload the next song
454
soundOnload: function() {
455
// context is that of the sound object
456
var that = this.u1msData.widget,
457
currentNode = this.u1msData.node,
458
cachedSound = that.cache.retrieve(that.getTrackId(currentNode));
460
if(cachedSound.response.readyState !== 2) {
462
this.onposition(this.duration - 5000, Y.bind(function() {
463
var nextNode = this.getNextNode(currentNode);
465
this.getOrCreateSound(nextNode);
470
// sound not loaded (readyState 2 == failed/error)
471
Y.Global.fire(that.get('oneError'), {message: "The selected song failed to load. Please try again later."});
473
// destroy the bad SM object
474
cachedSound.response.destruct();
476
// make this cache item go away now
477
cachedSound.expires = 1;
479
// unset currentSound
480
that.set('currentSound', undefined);
482
// remove active classes and such
483
that.clearActiveRow();
487
bindUnity: function() {
488
Y.log("Configuring Unity integration");
489
var mp = Y.Unity.Unity.MediaPlayer;
490
mp.init("Ubuntu One Music");
492
mp.setCanPause(true);
493
mp.setCanGoNext(true);
494
mp.setCanGoPrevious(true);
495
mp.setPlaybackState(mp.PlaybackState.PAUSED);
496
mp.onPlayPause(Y.bind(this.unityPlayPause, this));
497
mp.onNext(Y.bind(this.unityNext, this));
498
mp.onPrevious(Y.bind(this.unityPrevious, this));
501
unityPlayPause: function() {
502
Y.log("Sound Menu: play/pause");
503
var currentSound = this.get("currentSound"),
506
currentSound.togglePause();
509
// No current sound: start playing the first track.
510
node = this.getFirstNode();
512
currentSound = this.getOrCreateSound(node);
517
unityNext: function() {
518
Y.log("Sound Menu: next track");
519
var currentSound = this.get("currentSound"), node;
522
node = this.getNextNode(currentSound.u1msData.node);
524
node = this.getFirstNode();
527
currentSound = this.getOrCreateSound(node);
532
unityPrevious: function() {
533
Y.log("Sound Menu: previous track");
534
var currentSound = this.get("currentSound"), node;
537
node = this.getPrevNode(currentSound.u1msData.node);
539
node = this.getFirstNode();
542
currentSound = this.getOrCreateSound(node);
549
Y.namespace("U1MS").PreviewPlayer = PreviewPlayer;