Adding player
This commit is contained in:
parent
8e090908be
commit
8eedd88c88
@ -50,6 +50,7 @@
|
||||
<script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular-touch.js"></script>
|
||||
<script type="text/javascript" src="vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular-media-player/angular-media-player.js"></script>
|
||||
|
||||
<script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script>
|
||||
<script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script>
|
||||
|
@ -13,6 +13,7 @@ angular.module('myApp', [
|
||||
'ngSanitize',
|
||||
'ngTouch',
|
||||
'ui.bootstrap',
|
||||
'mediaPlayer',
|
||||
'izhukov.utils',
|
||||
'izhukov.mtproto',
|
||||
'izhukov.mtproto.wrapper',
|
||||
|
@ -1,13 +1,15 @@
|
||||
<div class="audio_player_wrap">
|
||||
<div class="audio_player_button">
|
||||
<div class="audio_player_wrap" ng-class="">
|
||||
<div class="audio_player_button" ng-click="togglePlay()">
|
||||
<i class="icon audio_player_btn_icon"></i>
|
||||
</div>
|
||||
<div class="audio_player_title_wrap">
|
||||
<div class="audio_player_title"></div>
|
||||
<div class="audio_player_meta"></div>
|
||||
<div class="audio_player_title" my-i18n="message_attach_audio_message"></div>
|
||||
<div class="audio_player_meta">
|
||||
<span class="audio_player_duration"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio_player_progress_wrap">
|
||||
<div class="audio_player_progress"></div>
|
||||
<i class="icon icon-player-track"></i>
|
||||
<div class="audio_player_progress" ng-style="{width: audio.progress.percent + '%'}"></div>
|
||||
<!-- <i class="icon icon-player-track"></i> -->
|
||||
</div>
|
||||
</div>
|
508
app/vendor/angular-media-player/angular-media-player.js
vendored
Normal file
508
app/vendor/angular-media-player/angular-media-player.js
vendored
Normal file
@ -0,0 +1,508 @@
|
||||
/**
|
||||
* MDN references for hackers:
|
||||
* ===========================
|
||||
* Media events on <audio> and <video> tags:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
|
||||
* Properties and Methods:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
|
||||
* Internet Exploder version:
|
||||
* http://msdn.microsoft.com/en-us/library/ff975061(v=vs.85).aspx
|
||||
*
|
||||
* Understanding TimeRanges objects:
|
||||
* http://html5doctor.com/html5-audio-the-state-of-play/
|
||||
*/
|
||||
angular.module('mediaPlayer', ['mediaPlayer.helpers'])
|
||||
.constant('mp.playerDefaults', {
|
||||
// general properties
|
||||
currentTrack: 0,
|
||||
ended: undefined,
|
||||
network: undefined,
|
||||
playing: false,
|
||||
seeking: false,
|
||||
tracks: 0,
|
||||
volume: 1,
|
||||
|
||||
// formatted properties
|
||||
formatDuration: '00:00',
|
||||
formatTime: '00:00',
|
||||
loadPercent: 0
|
||||
})
|
||||
|
||||
.directive('mediaPlayer', ['$rootScope', '$interpolate', '$timeout', 'mp.throttle', 'mp.playerDefaults',
|
||||
function ($rootScope, $interpolate, $timeout, throttle, playerDefaults) {
|
||||
|
||||
var playerMethods = {
|
||||
/**
|
||||
* @usage load([mediaElement], [autoplay]);
|
||||
*
|
||||
* @param {mediaElement Obj} mediaElement a single mediaElement, may contain multiple <source>(s)
|
||||
* @param {boolean} autoplay: flag to autostart loaded element
|
||||
*/
|
||||
load: function (mediaElement, autoplay) {
|
||||
// method overloading
|
||||
if (typeof mediaElement === 'boolean') {
|
||||
autoplay = mediaElement;
|
||||
mediaElement = null;
|
||||
} else if (typeof mediaElement === 'object') {
|
||||
this.$clearSourceList();
|
||||
this.$addSourceList(mediaElement);
|
||||
}
|
||||
this.$domEl.load();
|
||||
this.ended = undefined;
|
||||
if (autoplay) {
|
||||
this.$element.one('canplay', this.play.bind(this));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* resets the player (clear the <source>s) and reloads the playlist
|
||||
*
|
||||
* @usage reset([autoplay]);
|
||||
* @param {boolean} autoplay: flag to autoplay once resetted
|
||||
*/
|
||||
reset: function (autoplay) {
|
||||
angular.extend(this, playerDefaults);
|
||||
this.$clearSourceList();
|
||||
this.load(this.$playlist, autoplay);
|
||||
},
|
||||
/**
|
||||
* @usage play([index], [selectivePlay])
|
||||
* @param {integer} index: playlist index (0...n), to start playing from
|
||||
* @param {boolean} selectivePlay: only correct value is `true`, in which case will only play the specified,
|
||||
* or current, track. The default is to continue playing the next one.
|
||||
*/
|
||||
play: function (index, selectivePlay) {
|
||||
// method overloading
|
||||
if (typeof index === 'boolean') {
|
||||
selectivePlay = index;
|
||||
index = undefined;
|
||||
}
|
||||
if (selectivePlay) {
|
||||
this.$selective = true;
|
||||
}
|
||||
|
||||
if (this.$playlist.length > index) {
|
||||
this.currentTrack = index + 1;
|
||||
return this.load(this.$playlist[index], true);
|
||||
}
|
||||
// readyState = HAVE_NOTHING (0) means there's nothing into the <audio>/<video> tag
|
||||
if (!this.currentTrack && this.$domEl.readyState) { this.currentTrack++; }
|
||||
|
||||
// In case the stream is completed, reboot it with a load()
|
||||
if (this.ended) {
|
||||
this.load(true);
|
||||
} else {
|
||||
this.$domEl.play();
|
||||
}
|
||||
},
|
||||
playPause: function (index, selectivePlay) {
|
||||
// method overloading
|
||||
if (typeof index === 'boolean') {
|
||||
selectivePlay = index;
|
||||
index = undefined;
|
||||
}
|
||||
if (selectivePlay) {
|
||||
this.$selective = true;
|
||||
}
|
||||
|
||||
if (typeof index === 'number' && index + 1 !== this.currentTrack) {
|
||||
this.play(index);
|
||||
} else if (this.playing) {
|
||||
this.pause();
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
pause: function () {
|
||||
this.$domEl.pause();
|
||||
},
|
||||
stop: function () {
|
||||
this.reset();
|
||||
},
|
||||
toggleMute: function () {
|
||||
this.muted = this.$domEl.muted = !this.$domEl.muted;
|
||||
},
|
||||
next: function (autoplay) {
|
||||
var self = this;
|
||||
if (self.currentTrack && self.currentTrack < self.tracks) {
|
||||
var wasPlaying = autoplay || self.playing;
|
||||
self.pause();
|
||||
$timeout(function () {
|
||||
self.$clearSourceList();
|
||||
self.$addSourceList(self.$playlist[self.currentTrack]);
|
||||
self.load(wasPlaying); // setup autoplay here.
|
||||
self.currentTrack++;
|
||||
});
|
||||
}
|
||||
},
|
||||
prev: function (autoplay) {
|
||||
var self = this;
|
||||
if (self.currentTrack && self.currentTrack - 1) {
|
||||
var wasPlaying = autoplay || self.playing;
|
||||
self.pause();
|
||||
$timeout(function () {
|
||||
self.$clearSourceList();
|
||||
self.$addSourceList(self.$playlist[self.currentTrack - 2]);
|
||||
self.load(wasPlaying); // setup autoplay here.
|
||||
self.currentTrack--;
|
||||
});
|
||||
}
|
||||
},
|
||||
setPlaybackRate: function (value) {
|
||||
this.$domEl.playbackRate = value;
|
||||
},
|
||||
setVolume: function (value) {
|
||||
this.$domEl.volume = value;
|
||||
},
|
||||
seek: function (value) {
|
||||
var doubleval = 0, valuesArr;
|
||||
if (typeof value === 'string') {
|
||||
valuesArr = value.split(':');
|
||||
doubleval += parseInt(valuesArr.pop(), 10);
|
||||
if (valuesArr.length) { doubleval += parseInt(valuesArr.pop(), 10) * 60; }
|
||||
if (valuesArr.length) { doubleval += parseInt(valuesArr.pop(), 10) * 3600; }
|
||||
if (!isNaN(doubleval)) {
|
||||
return this.$domEl.currentTime = doubleval;
|
||||
}
|
||||
} else {
|
||||
return this.$domEl.currentTime = value;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* binds a specific event directly to the element
|
||||
* @param {string} type event name
|
||||
* @param {function} fn callback
|
||||
* @return {function} unbind function, call it in order to remove the listener
|
||||
*/
|
||||
on: function (type, fn) {
|
||||
return this.$element.on(type, fn);
|
||||
},
|
||||
off: function (type, fn) {
|
||||
return this.$element.off(type, fn);
|
||||
},
|
||||
one: function (type, fn) {
|
||||
return this.$element.one(type, fn);
|
||||
},
|
||||
$addSourceList: function (sourceList) {
|
||||
var self = this;
|
||||
if (angular.isArray(sourceList)) {
|
||||
angular.forEach(sourceList, function (singleElement, index) {
|
||||
var sourceElem = document.createElement('SOURCE');
|
||||
['src', 'type', 'media'].forEach(function (key) { // use only a subset of the properties
|
||||
if (singleElement[key] !== undefined) { // firefox is picky if you set undefined attributes
|
||||
sourceElem.setAttribute(key, singleElement[key]);
|
||||
}
|
||||
});
|
||||
self.$element.append(sourceElem);
|
||||
});
|
||||
} else if (angular.isObject(sourceList)) {
|
||||
var sourceElem = document.createElement('SOURCE');
|
||||
['src', 'type', 'media'].forEach(function (key) {
|
||||
if (sourceList[key] !== undefined) {
|
||||
sourceElem.setAttribute(key, sourceList[key]);
|
||||
}
|
||||
});
|
||||
self.$element.append(sourceElem);
|
||||
}
|
||||
},
|
||||
$clearSourceList: function () {
|
||||
this.$element.contents().remove();
|
||||
},
|
||||
$formatTime: function (seconds) {
|
||||
if (seconds === Infinity) {
|
||||
return '∞'; // If the data is streaming
|
||||
}
|
||||
var hours = parseInt(seconds / 3600, 10) % 24,
|
||||
minutes = parseInt(seconds / 60, 10) % 60,
|
||||
secs = parseInt(seconds % 60, 10),
|
||||
result,
|
||||
fragment = (minutes < 10 ? '0' + minutes : minutes) + ':' + (secs < 10 ? '0' + secs : secs);
|
||||
if (hours > 0) {
|
||||
result = (hours < 10 ? '0' + hours : hours) + ':' + fragment;
|
||||
} else {
|
||||
result = fragment;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
$attachPlaylist: function (pl) {
|
||||
if (pl === undefined || pl === null) {
|
||||
this.playlist = [];
|
||||
} else {
|
||||
this.$playlist = pl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Binding function that gives life to AngularJS scope
|
||||
* @param {Scope} au Player Scope
|
||||
* @param {HTMLMediaElement} HTML5 element
|
||||
* @param {jQlite} element <audio>/<video> element
|
||||
* @return {function}
|
||||
*
|
||||
* Returns an unbinding function
|
||||
*/
|
||||
var bindListeners = function (au, al, element) {
|
||||
var listeners = {
|
||||
playing: function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.playing = true;
|
||||
scope.ended = false;
|
||||
});
|
||||
},
|
||||
pause: function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.playing = false;
|
||||
});
|
||||
},
|
||||
ended: function () {
|
||||
if (!au.$selective && au.currentTrack < au.tracks) {
|
||||
au.next(true);
|
||||
} else {
|
||||
au.$apply(function (scope) {
|
||||
scope.ended = true;
|
||||
scope.playing = false; // IE9 does not throw 'pause' when file ends
|
||||
});
|
||||
}
|
||||
},
|
||||
timeupdate: throttle(1000, false, function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.currentTime = al.currentTime;
|
||||
scope.formatTime = scope.$formatTime(scope.currentTime);
|
||||
});
|
||||
}),
|
||||
loadedmetadata: function () {
|
||||
au.$apply(function (scope) {
|
||||
if (!scope.currentTrack) { scope.currentTrack++; } // This is triggered *ONLY* the first time a <source> gets loaded.
|
||||
scope.duration = al.duration;
|
||||
scope.formatDuration = scope.$formatTime(scope.duration);
|
||||
if (al.buffered.length) {
|
||||
scope.loadPercent = Math.round((al.buffered.end(al.buffered.length - 1) / scope.duration) * 100);
|
||||
}
|
||||
});
|
||||
},
|
||||
progress: function () {
|
||||
if (au.$domEl.buffered.length) {
|
||||
au.$apply(function (scope) {
|
||||
scope.loadPercent = Math.round((al.buffered.end(al.buffered.length - 1) / scope.duration) * 100);
|
||||
scope.network = 'progress';
|
||||
});
|
||||
}
|
||||
},
|
||||
volumechange: function () { // Sent when volume changes (both when the volume is set and when the muted attribute is changed).
|
||||
au.$apply(function (scope) {
|
||||
// scope.volume = Math.floor(al.volume * 100);
|
||||
scope.volume = al.volume;
|
||||
scope.muted = al.muted;
|
||||
});
|
||||
},
|
||||
seeked: function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.seeking = false;
|
||||
});
|
||||
},
|
||||
seeking: function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.seeking = true;
|
||||
});
|
||||
},
|
||||
ratechange: function () {
|
||||
au.$apply(function (scope) {
|
||||
// scope.playbackRate = Math.floor(al.playbackRate * 100);
|
||||
scope.playbackRate = al.playbackRate;
|
||||
});
|
||||
},
|
||||
stalled: function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.network = 'stalled';
|
||||
});
|
||||
},
|
||||
suspend: function () {
|
||||
au.$apply(function (scope) {
|
||||
scope.network = 'suspend';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
angular.forEach(listeners, function (f, listener) {
|
||||
element.on(listener, f);
|
||||
});
|
||||
};
|
||||
|
||||
var MediaPlayer = function (element) {
|
||||
var mediaScope = angular.extend($rootScope.$new(true), {
|
||||
$element: element,
|
||||
$domEl: element[0],
|
||||
$playlist: undefined,
|
||||
|
||||
// bind TimeRanges structures to actual MediaElement
|
||||
buffered: element[0].buffered,
|
||||
played: element[0].played,
|
||||
seekable: element[0].seekable,
|
||||
}, playerDefaults, playerMethods);
|
||||
bindListeners(mediaScope, element[0], element);
|
||||
return mediaScope;
|
||||
};
|
||||
|
||||
// creates a watch function bound to the specific player
|
||||
// optimizable: closures eats ram
|
||||
function playlistWatch(player) {
|
||||
return function (playlistNew, playlistOld, watchScope) {
|
||||
var currentTrack,
|
||||
newTrackNum = null;
|
||||
|
||||
// on every playlist change, it refreshes the reference, safer/shorter approach
|
||||
// than using multiple ifs and refresh only if it changed; there's no benefit in that
|
||||
player.$attachPlaylist(playlistNew);
|
||||
if (playlistNew === undefined && playlistOld !== undefined) {
|
||||
return player.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Playlist update logic:
|
||||
* If the player has started ->
|
||||
* Check if the playing track is in the new Playlist [EXAMPLE BELOW]
|
||||
* If it is ->
|
||||
* Assign to it the new tracknumber
|
||||
* Else ->
|
||||
* If the new Playlist has some song ->
|
||||
* Pause the player, and get the new Playlist
|
||||
* Else ->
|
||||
* Reset the player, and await for orders
|
||||
*
|
||||
* Else (if the player hasn't started yet)
|
||||
* Just replace the <src> tags
|
||||
*
|
||||
* Example
|
||||
* playlist: [a,b,c], playing: c, trackNum: 2
|
||||
* ----delay 5 sec-----
|
||||
* playlist: [f,a,b,c], playing: c, trackNum: 3
|
||||
*
|
||||
*/
|
||||
if (player.currentTrack) {
|
||||
currentTrack = playlistOld ? playlistOld[player.currentTrack - 1] : -1;
|
||||
for (var i = 0; i < playlistNew.length; i++) {
|
||||
if (angular.equals(playlistNew[i], currentTrack)) { newTrackNum = i; break; }
|
||||
}
|
||||
if (newTrackNum !== null) { // currentTrack it's still in the new playlist, update trackNumber
|
||||
player.currentTrack = newTrackNum + 1;
|
||||
player.tracks = playlistNew.length;
|
||||
} else { // currentTrack has been removed.
|
||||
player.pause();
|
||||
if (playlistNew.length) { // if the new playlist has some elements, replace actual.
|
||||
$timeout(function () { // need $timeout because the mediaTag needs a little time to launch the 'pause' event
|
||||
player.$clearSourceList();
|
||||
player.$addSourceList(playlistNew[0]);
|
||||
player.load();
|
||||
player.tracks = playlistNew.length;
|
||||
});
|
||||
} else { // the new playlist has no elements, clear actual
|
||||
player.reset();
|
||||
}
|
||||
}
|
||||
} else if (playlistNew.length) { // new playlist has elements, load them
|
||||
player.$clearSourceList();
|
||||
player.$addSourceList(playlistNew[0]);
|
||||
player.load();
|
||||
player.tracks = playlistNew.length;
|
||||
} else { // new playlist has no elements, clear actual
|
||||
player.reset();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* The directive uses the Scope in which gets declared,
|
||||
* so it can read/write it with ease.
|
||||
* The only isolated Scope here gets created for the MediaPlayer.
|
||||
*/
|
||||
scope: false,
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
var playlistName = attrs.playlist,
|
||||
mediaName = attrs.mediaPlayer || attrs.playerControl;
|
||||
|
||||
var player = new MediaPlayer(element), playlist = scope[playlistName];
|
||||
// create data-structures in the father Scope
|
||||
if (playlistName === undefined) {
|
||||
playlist = []; // local playlist gets defined as new
|
||||
} else if (scope[playlistName] === undefined) {
|
||||
playlist = scope[playlistName] = []; // define playlist on father scope
|
||||
} else {
|
||||
playlist = scope[playlistName];
|
||||
}
|
||||
if (mediaName !== undefined) { scope[mediaName] = player; }
|
||||
|
||||
if (element[0].tagName !== 'AUDIO' && element[0].tagName !== 'VIDEO') {
|
||||
return new Error('player directive works only when attached to an <audio>/<video> type tag');
|
||||
}
|
||||
var mediaElement = [],
|
||||
sourceElements = element.find('source');
|
||||
|
||||
// create a single playlist element from <source> tag(s).
|
||||
// if the <source> tag is one, use object notation...
|
||||
if (sourceElements.length === 1) {
|
||||
playlist.unshift({ src: sourceElements[0].src, type: sourceElements[0].type, media: sourceElements[0].media });
|
||||
} else if (sourceElements.length > 1) { // otherwise use array notation
|
||||
angular.forEach(sourceElements, function (sourceElement) {
|
||||
mediaElement.push({ src: sourceElement.src, type: sourceElement.type, media: sourceElement.media });
|
||||
});
|
||||
// put mediaElement as first element in the playlist
|
||||
playlist.unshift(mediaElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user wants to keep track of the playlist changes
|
||||
* has to use data-playlist="variableName" in the <audio>/<video> tag
|
||||
*
|
||||
* Otherwise: it will be created an empty playlist and attached to the player.
|
||||
*/
|
||||
if (playlistName === undefined) {
|
||||
player.$attachPlaylist(playlist); // empty playlist case
|
||||
} else if (playlist.length) {
|
||||
playlistWatch(player)(playlist, undefined, scope); // playlist already populated gets bootstrapped
|
||||
scope.$watch(playlistName, playlistWatch(player), true); // then watch gets applied
|
||||
} else {
|
||||
scope.$watch(playlistName, playlistWatch(player), true); // playlist empty, only watch
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}]
|
||||
);
|
||||
|
||||
angular.module('mediaPlayer.helpers', [])
|
||||
.factory('mp.throttle', ['$timeout', function ($timeout) {
|
||||
return function (delay, no_trailing, callback, debounce_mode) {
|
||||
var timeout_id,
|
||||
last_exec = 0;
|
||||
|
||||
if (typeof no_trailing !== 'boolean') {
|
||||
debounce_mode = callback;
|
||||
callback = no_trailing;
|
||||
no_trailing = undefined;
|
||||
}
|
||||
|
||||
var wrapper = function () {
|
||||
var that = this,
|
||||
elapsed = +new Date() - last_exec,
|
||||
args = arguments,
|
||||
exec = function () {
|
||||
last_exec = +new Date();
|
||||
callback.apply(that, args);
|
||||
},
|
||||
clear = function () {
|
||||
timeout_id = undefined;
|
||||
};
|
||||
|
||||
if (debounce_mode && !timeout_id) { exec(); }
|
||||
if (timeout_id) { $timeout.cancel(timeout_id); }
|
||||
if (debounce_mode === undefined && elapsed > delay) {
|
||||
exec();
|
||||
} else if (no_trailing !== true) {
|
||||
timeout_id = $timeout(debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay);
|
||||
}
|
||||
};
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
}]);
|
@ -1,6 +1,6 @@
|
||||
CACHE MANIFEST
|
||||
|
||||
# 33
|
||||
# 34
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user