~jonas-drange/online-services-common-js/navbar-autocomplete

« back to all changes in this revision

Viewing changes to src/musicstore/js/musicstore-player.js

  • Committer: Stephen Stewart
  • Date: 2014-02-22 23:57:25 UTC
  • mfrom: (18.1.2 trunk)
  • Revision ID: stephen.stewart@canonical.com-20140222235725-iw6f15t9umws19xd
mergeĀ lp:~stephen-stewart/online-services-common-js/remove-u1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Any frequently used shortcuts, strings and constants */
2
 
 
3
 
/* encourage jslint not to worry about global soundManager */
4
 
/*global soundManager: false */
5
 
var sm = soundManager;
6
 
 
7
 
/* PreviewPlayer class constructor */
8
 
function PreviewPlayer() {
9
 
    PreviewPlayer.superclass.constructor.apply(this, arguments);
10
 
}
11
 
 
12
 
PreviewPlayer.NAME = "previewPlayer";
13
 
 
14
 
/** hash of static CSS class names
15
 
 *
16
 
 * @property CSS_CLASSES
17
 
 * @type {Object}
18
 
 * @private
19
 
 * @static
20
 
 */
21
 
PreviewPlayer.CSS_CLASSES = {
22
 
    previewLink: ".preview",
23
 
    previewCtrl: ".preview .ctrl",
24
 
    previewText: ".preview .acchide",
25
 
    previewPlayClass: "preview-play",
26
 
    previewPauseClass: "preview-pause",
27
 
    rowSel: "li",
28
 
    trackNum: ".track-num",
29
 
    trackNumContainer: ".num",
30
 
    vu: ".vu",
31
 
    vuLeft: ".left",
32
 
    vuRight: ".right",
33
 
    progressBar: ".progressbar",
34
 
    progressPlay: ".play",
35
 
    progressLoad: ".load"
36
 
};
37
 
 
38
 
PreviewPlayer.ATTRS = {
39
 
    /**
40
 
     * width of row as apposed to width of contentBox
41
 
     * TODO consider using default 'width' ATTR
42
 
     */
43
 
    rowWidth: {
44
 
        value: "" // default value of player width
45
 
    },
46
 
    oneError: {
47
 
        value: "one:error"
48
 
    }
49
 
};
50
 
 
51
 
PreviewPlayer.HTML_PARSER = {
52
 
    rowWidth: function(srcNode) {
53
 
        return srcNode.get('offsetWidth');
54
 
    }
55
 
};
56
 
 
57
 
Y.extend(PreviewPlayer, Y.Widget, {
58
 
 
59
 
    CONTENT_TEMPLATE: null,
60
 
 
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
64
 
        // we keep.
65
 
        this.cache = new Y.Cache({max:10, uniqueKeys: true});
66
 
    },
67
 
 
68
 
    renderUI : function() {
69
 
        var vuTemplate = Y.one("#tpl-vu-meter"),
70
 
            progressbarTemplate = Y.one("#tpl-progressbar");
71
 
 
72
 
        this.set("vuMeter", Y.Node.create(vuTemplate.getContent()));
73
 
        this.set("progress", Y.Node.create(progressbarTemplate.getContent()));
74
 
    },
75
 
 
76
 
    bindUI : function() {
77
 
        var srcNode = this.get("srcNode");
78
 
        srcNode.delegate("click", this.handleClick, PreviewPlayer.CSS_CLASSES.previewLink, this);
79
 
 
80
 
 
81
 
        Y.on("windowresize", Y.bind(this.handleResize, this));
82
 
 
83
 
        this.after('rowWidthChange', this.afterTrWidthChange);
84
 
 
85
 
        // perform Unity integration if required.
86
 
        Y.Unity.onInit(Y.bind(this.bindUnity, this));
87
 
    },
88
 
 
89
 
    // Beyond this point is the PreviewPlayer specific application and rendering logic
90
 
 
91
 
    handleClick: function(e) {
92
 
        e.halt();
93
 
        var target = e.currentTarget,
94
 
            currentSound = this.get("currentSound"),
95
 
            sound = this.getOrCreateSound(target);
96
 
 
97
 
        if (sound === currentSound) {
98
 
            sound.togglePause();
99
 
        } else {
100
 
            if (currentSound) {
101
 
                currentSound.stop();
102
 
            }
103
 
            if (sound) {
104
 
                sound.play();
105
 
            }
106
 
        }
107
 
    },
108
 
 
109
 
    /**
110
 
     * update the rowWidth attribute when a resize event is fired
111
 
     */
112
 
    handleResize: function() {
113
 
        var srcNode = this.get("srcNode");
114
 
        return this.set('rowWidth', srcNode.one('li').get('offsetWidth'));
115
 
    },
116
 
    /**
117
 
     * handle change to rowWidth attr
118
 
     */
119
 
    afterTrWidthChange: function() {
120
 
        var bar = this.get("progressBar");
121
 
        if (bar) {
122
 
            bar.setStyle("width", this.get("rowWidth")+"px");
123
 
        }
124
 
    },
125
 
    getTrackId: function(node) {
126
 
        return "u1ms" + node.getAttribute("data-u1ms-trackid");
127
 
    },
128
 
    getTrackUrl: function(node){
129
 
        return node.getAttribute("href");
130
 
    },
131
 
    getOrCreateSound: function(node) {
132
 
        var id = this.getTrackId(node),
133
 
            url = this.getTrackUrl(node),
134
 
            sound, cachedSound;
135
 
 
136
 
        if (id && url) {
137
 
            cachedSound = this.cache.retrieve(id);
138
 
            if (cachedSound === null) {
139
 
                sound = sm.createSound({
140
 
                    id: id,
141
 
                    url: url,
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,
150
 
                    multiShot: false,
151
 
                    usePeakData: true
152
 
                });
153
 
                sound.u1msData = {
154
 
                    node: node,
155
 
                    widget: this,
156
 
                    vu: {
157
 
                        node: null,
158
 
                        leftVu: null,
159
 
                        rightVu: null
160
 
                    },
161
 
                    throbbing: false
162
 
                };
163
 
                this.cache.add(id, sound);
164
 
            } else {
165
 
                sound = cachedSound.response;
166
 
            }
167
 
            return sound;
168
 
        }
169
 
    },
170
 
    /*
171
 
    * Preload the next track in the list
172
 
    */
173
 
    getFirstNode: function() {
174
 
        var srcNode = this.get("srcNode");
175
 
        return srcNode.one(PreviewPlayer.CSS_CLASSES.previewLink);
176
 
    },
177
 
 
178
 
    getNextNode: function(currentNode) {
179
 
        var nextRow,
180
 
            currentRow;
181
 
 
182
 
        currentRow = currentNode.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
183
 
        nextRow = currentRow.next(PreviewPlayer.CSS_CLASSES.rowSel);
184
 
 
185
 
        if (nextRow) {
186
 
            return nextRow.one(PreviewPlayer.CSS_CLASSES.previewLink);
187
 
        }
188
 
 
189
 
        return false;
190
 
    },
191
 
    getPrevNode: function(currentNode) {
192
 
        var prevRow,
193
 
            currentRow;
194
 
 
195
 
        currentRow = currentNode.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
196
 
        prevRow = currentRow.previous(PreviewPlayer.CSS_CLASSES.rowSel);
197
 
 
198
 
        if (prevRow) {
199
 
            return prevRow.one(PreviewPlayer.CSS_CLASSES.previewLink);
200
 
        }
201
 
 
202
 
        return false;
203
 
    },
204
 
    setActiveRow: function(currentNode, sound) {
205
 
        var currentRow = currentNode.ancestor(PreviewPlayer.CSS_CLASSES.rowSel),
206
 
            previewText, previewCtrl, vu, vuLeft, vuRight, progress;
207
 
 
208
 
        currentRow.addClass("active");
209
 
        previewText = currentRow.one(PreviewPlayer.CSS_CLASSES.previewText);
210
 
        if (previewText) {
211
 
            previewText.setContent("Pause");
212
 
        }
213
 
        previewCtrl = currentRow.one(PreviewPlayer.CSS_CLASSES.previewCtrl);
214
 
        if (previewCtrl) {
215
 
            previewCtrl.removeClass(PreviewPlayer.CSS_CLASSES.previewPlayClass);
216
 
            previewCtrl.addClass(PreviewPlayer.CSS_CLASSES.previewPauseClass);
217
 
        }
218
 
 
219
 
 
220
 
        if (!this.get("progressBar")) {
221
 
            progress = this.get("progress");
222
 
 
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));
226
 
 
227
 
            currentRow.appendChild(progress);
228
 
        }
229
 
 
230
 
        if (sound.isHTML5) {
231
 
            currentRow.appendChild('<span class="vu vu-dummy"></span>');
232
 
        } else {
233
 
            vu = this.get("vuMeter");
234
 
            if (vu) {
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);
240
 
                }
241
 
                currentRow.appendChild(vu);
242
 
            }
243
 
        }
244
 
    },
245
 
    clearActiveRow: function() {
246
 
        var srcNode = this.get("srcNode"),
247
 
            currentRow = srcNode.one(".active"),
248
 
            currentNode, previewText, previewCtrl, vu, progress, pm, lm;
249
 
        if (currentRow) {
250
 
            
251
 
            currentNode = currentRow.one(PreviewPlayer.CSS_CLASSES.previewLink);
252
 
 
253
 
            currentRow.removeClass("active");
254
 
 
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");
258
 
            if (pm) {
259
 
                pm.setStyle('width', '0');
260
 
            }
261
 
 
262
 
            lm = this.get("loadMeter");
263
 
            if (lm) {
264
 
                lm.setStyle('width', '0');
265
 
            }
266
 
 
267
 
            previewText = currentRow.one(PreviewPlayer.CSS_CLASSES.previewText);
268
 
            if (previewText) {
269
 
                previewText.setContent("Play");
270
 
            }
271
 
            previewCtrl = currentRow.one(PreviewPlayer.CSS_CLASSES.previewCtrl);
272
 
            if (previewCtrl) {
273
 
                previewCtrl.removeClass(PreviewPlayer.CSS_CLASSES.previewPauseClass);
274
 
                previewCtrl.addClass(PreviewPlayer.CSS_CLASSES.previewPlayClass);
275
 
            }
276
 
            vu = currentRow.one(PreviewPlayer.CSS_CLASSES.vu);
277
 
            if (vu) {
278
 
                vu.remove();
279
 
            }
280
 
            progress = currentRow.one(PreviewPlayer.CSS_CLASSES.progressBar);
281
 
            if (progress) {
282
 
                progress.remove();
283
 
            }
284
 
        }
285
 
    },
286
 
    showThrobber: function (sound) {
287
 
        var currentRow = sound.u1msData.node.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
288
 
        currentRow.addClass('loading');
289
 
        sound.u1msData.throbbing = true;
290
 
    },
291
 
    removeThrobber: function (sound) {
292
 
        var currentRow,
293
 
            throbber;
294
 
        
295
 
        // if we're not throbbing, do not do anything
296
 
        if(sound.u1msData.throbbing === false) {
297
 
            return;
298
 
        }
299
 
 
300
 
        currentRow = sound.u1msData.node.ancestor(PreviewPlayer.CSS_CLASSES.rowSel);
301
 
        currentRow.removeClass('loading');
302
 
        
303
 
        // if theres a throbber, remove it
304
 
        throbber = currentRow.one('.throbber');
305
 
        if(throbber) {
306
 
            throbber.remove();
307
 
        }
308
 
        
309
 
        sound.u1msData.throbbing = false;
310
 
    },
311
 
    soundPlay: function() {
312
 
        // context is that of the sound object
313
 
        var that = this.u1msData.widget,
314
 
            currentNode = this.u1msData.node,
315
 
            mp,
316
 
            trackInfo;
317
 
 
318
 
        that.set("currentSound", this);
319
 
        that.clearActiveRow();
320
 
        
321
 
        // song not loaded, show throbber
322
 
        if(this.readyState !== 3) {
323
 
            that.showThrobber(this);
324
 
        }
325
 
        
326
 
        that.setActiveRow(currentNode, this);
327
 
        if (Y.Unity.ready) {
328
 
            mp = Y.Unity.Unity.MediaPlayer;
329
 
            mp.setPlaybackState(mp.PlaybackState.PLAYING);
330
 
            trackInfo = {
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")
335
 
            };
336
 
            mp.setTrack(trackInfo);
337
 
            // And show a notification
338
 
            Y.Unity.Unity.Notification.showNotification(
339
 
                trackInfo.title,
340
 
                "by " + trackInfo.artist + " from " + trackInfo.album,
341
 
                trackInfo.artLocation);
342
 
        }
343
 
    },
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);
350
 
    },
351
 
    soundPause: function() {
352
 
        // context is that of the sound object
353
 
        var that = this.u1msData.widget,
354
 
            mp;
355
 
 
356
 
        that.clearActiveRow();
357
 
        if (Y.Unity.ready) {
358
 
            mp = Y.Unity.Unity.MediaPlayer;
359
 
            mp.setPlaybackState(mp.PlaybackState.PAUSED);
360
 
        }
361
 
    },
362
 
    soundStop: function() {
363
 
        // context is that of the sound object
364
 
        var that = this.u1msData.widget,
365
 
            mp;
366
 
 
367
 
        that.clearActiveRow();
368
 
        if (Y.Unity.ready) {
369
 
            mp = Y.Unity.Unity.MediaPlayer;
370
 
            mp.setPlaybackState(mp.PlaybackState.PAUSED);
371
 
        }
372
 
    },
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),
378
 
            nextSound;
379
 
        if (next) {
380
 
            nextSound = that.getOrCreateSound(next);
381
 
            nextSound.play();
382
 
        } else {
383
 
            that.clearActiveRow();
384
 
        }
385
 
    },
386
 
    soundWhileLoading: function() {
387
 
        var that = this.u1msData.widget;
388
 
        that.updateLoadMeter(this.bytesLoaded, this.bytesTotal);
389
 
    },
390
 
    soundWhilePlaying: function() {
391
 
        // context is that of the sound object
392
 
        var that = this.u1msData.widget;
393
 
        
394
 
        // in html5 mode this is called prematurely
395
 
        // so skip that fired event altogheter
396
 
        if(this.isHTML5 && this.position === 0) {
397
 
            return;
398
 
        }
399
 
        
400
 
        // throbber should never exist while playing
401
 
        that.removeThrobber(this);
402
 
        
403
 
        if (sm.flashVersion >= 9 && this.instanceOptions.usePeakData) {
404
 
            that.updatePeakData(this);
405
 
        }
406
 
        that.updatePlayMeter(this.position, this.durationEstimate);
407
 
    },
408
 
    updatePeakData: function(sound) {
409
 
        var vu = sound.u1msData.vu;
410
 
 
411
 
        if (vu.node) {
412
 
            vu.vuLeft.setStyle('height', Math.ceil(17*sound.peakData.left)+'px');
413
 
            vu.vuRight.setStyle('height', Math.ceil(17*sound.peakData.right)+'px');
414
 
        } else {
415
 
            // vus not set
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);
419
 
        }
420
 
        
421
 
    },
422
 
    /**
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
427
 
     */
428
 
    updateLoadMeter: function(current, total) {
429
 
        var width = this.get("rowWidth"),
430
 
            ratio;
431
 
 
432
 
        ratio = (current/total);
433
 
 
434
 
        if (ratio === 1) {
435
 
            width = '100%';
436
 
        } else {
437
 
            width = Math.floor(ratio * width) + 'px';
438
 
        }
439
 
        this.get("loadMeter").setStyle("width", width);
440
 
    },
441
 
    /**
442
 
     * update the display of the play meter
443
 
     * @param current {Number} bytes played
444
 
     * @param total {Number} total bytes to play
445
 
     */
446
 
    updatePlayMeter: function(current, total) {
447
 
        var width = this.get('rowWidth');
448
 
 
449
 
        width *= (current/total);
450
 
        this.get('playMeter').setStyle('width', Math.floor(width) +'px');
451
 
    },
452
 
 
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));
459
 
        
460
 
        if(cachedSound.response.readyState !== 2) {
461
 
            // sound loaded
462
 
            this.onposition(this.duration - 5000, Y.bind(function() {
463
 
                var nextNode = this.getNextNode(currentNode);
464
 
                if (nextNode) {
465
 
                    this.getOrCreateSound(nextNode);
466
 
                }
467
 
            }, that));
468
 
        }
469
 
        else {
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."});
472
 
            
473
 
            // destroy the bad SM object
474
 
            cachedSound.response.destruct();
475
 
            
476
 
            // make this cache item go away now
477
 
            cachedSound.expires = 1;
478
 
            
479
 
            // unset currentSound
480
 
            that.set('currentSound', undefined);
481
 
            
482
 
            // remove active classes and such
483
 
            that.clearActiveRow();
484
 
        }
485
 
    },
486
 
 
487
 
    bindUnity: function() {
488
 
        Y.log("Configuring Unity integration");
489
 
        var mp = Y.Unity.Unity.MediaPlayer;
490
 
        mp.init("Ubuntu One Music");
491
 
        mp.setCanPlay(true);
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));
499
 
    },
500
 
 
501
 
    unityPlayPause: function() {
502
 
        Y.log("Sound Menu: play/pause");
503
 
        var currentSound = this.get("currentSound"),
504
 
            node;
505
 
        if (currentSound) {
506
 
            currentSound.togglePause();
507
 
            return;
508
 
        }
509
 
        // No current sound: start playing the first track.
510
 
        node = this.getFirstNode();
511
 
        if (node) {
512
 
            currentSound = this.getOrCreateSound(node);
513
 
            currentSound.play();
514
 
        }
515
 
    },
516
 
 
517
 
    unityNext: function() {
518
 
        Y.log("Sound Menu: next track");
519
 
        var currentSound = this.get("currentSound"), node;
520
 
        if (currentSound) {
521
 
            currentSound.stop();
522
 
            node = this.getNextNode(currentSound.u1msData.node);
523
 
        } else {
524
 
            node = this.getFirstNode();
525
 
        }
526
 
        if (node) {
527
 
            currentSound = this.getOrCreateSound(node);
528
 
            currentSound.play();
529
 
        }
530
 
    },
531
 
 
532
 
    unityPrevious: function() {
533
 
        Y.log("Sound Menu: previous track");
534
 
        var currentSound = this.get("currentSound"), node;
535
 
        if (currentSound) {
536
 
            currentSound.stop();
537
 
            node = this.getPrevNode(currentSound.u1msData.node);
538
 
        } else {
539
 
            node = this.getFirstNode();
540
 
        }
541
 
        if (node) {
542
 
            currentSound = this.getOrCreateSound(node);
543
 
            currentSound.play();
544
 
        }
545
 
    }
546
 
 
547
 
});
548
 
 
549
 
Y.namespace("U1MS").PreviewPlayer = PreviewPlayer;