Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[newui] Album art cache.

  • Loading branch information...
commit d4dfff6eae957818b84dea750d736ce1eb001244 1 parent 05f0b45
@klange klange authored
View
4 bin/initdb-mysql.pl
@@ -16,6 +16,7 @@
$db->do("DROP TABLE IF EXISTS players");
$db->do("DROP TABLE IF EXISTS playlists");
$db->do("DROP TABLE IF EXISTS playlist_contents");
+$db->do("DROP TABLE IF EXISTS art_cache");
$db->do("CREATE TABLE songs (song_id INT UNSIGNED AUTO_INCREMENT, path
VARCHAR(1024) NOT NULL, artist VARCHAR(256), albumartist VARCHAR(256), album VARCHAR(256), title
@@ -37,3 +38,6 @@
$db->do("CREATE TABLE playlist_contents (playlist_id INT UNSIGNED, song_id INT
UNSIGNED, priority INT, UNIQUE(playlist_id,song_id))");
+
+$db->do("CREATE TABLE art_cache (artist VARCHAR(256), album VARCHAR(256), title VARCHAR(256),
+ image BLOB, url VARCHAR(512))");
View
8 bin/initdb-sqlite.pl
@@ -16,6 +16,7 @@
$db->do("DROP TABLE IF EXISTS players");
$db->do("DROP TABLE IF EXISTS playlists");
$db->do("DROP TABLE IF EXISTS playlist_contents");
+$db->do("DROP TABLE IF EXISTS art_cache");
$db->do("CREATE TABLE songs (song_id INTEGER PRIMARY KEY AUTOINCREMENT, path
VARCHAR(1024) NOT NULL, artist VARCHAR(256), albumartist VARCHAR(256), album VARCHAR(256), title
@@ -35,5 +36,8 @@
$db->do("CREATE TABLE playlists (who VARCHAR(256) NOT NULL, playlist_id INTEGER
PRIMARY KEY AUTOINCREMENT, title VARCHAR(256) NOT NULL)");
-$db->do("CREATE TABLE playlist_contents (playlist_id INT, song_id INT
- , priority INT, UNIQUE(playlist_id,song_id))");
+$db->do("CREATE TABLE playlist_contents (playlist_id INT, song_id INT,
+ priority INT, UNIQUE(playlist_id,song_id))");
+
+$db->do("CREATE TABLE art_cache (artist VARCHAR(256), album VARCHAR(256), title VARCHAR(256),
+ image BLOB, url VARCHAR(512))");
View
13 index2.html
@@ -12,8 +12,9 @@
<script src="www-data/jquery-ui-1.8.6.custom.min.js"></script>
<script src="www-data/jquery.tablesorter.min.js"></script>
<script src="www-data/jquery.address-1.3.min.js"></script>
- <script src="www-data/acoustics2.js"></script>
<script src="www-data/jquery.reflection.js"></script>
+ <script src="www-data/jquery.favicon.js"></script>
+ <script src="www-data/acoustics2.js"></script>
</head>
<body>
<div id="header-bar">
@@ -271,6 +272,14 @@
<a href="javascript:unfullscreen()" class="control-button button-link">Close</a>
</div>
</div>
- <div id="messageBox">no text... why?</div>
+ <div id="message-box">
+ <div id="message-box-inner">
+ <h1 id="message-box-title">Title</h1>
+ <span id="message-box-message">Content</span>
+ </div>
+ <div id="message-box-close">
+ <a class="control-button button-link" href="javascript:closeMessageBox();">Okay</a>
+ </div>
+ </div>
</body>
</html>
View
31 json.pl
@@ -34,19 +34,28 @@
my $mode = lc($q->param('mode') || '');
$mode = 'status' if $mode =~ /^_/ or $mode =~ /[^\w_]/ or $mode eq 'new';
$mode = 'status' unless $web->can($mode);
+
my($headers, $data) = $web->$mode;
- $q->no_cache(1);
- binmode STDOUT, ':utf8';
- print $q->header(
- @$headers,
- -type => 'application/json',
- );
- print scalar JSON::DWIW->new({
- pretty => 1,
- escape_multi_byte => 1,
- bad_char_policy => 'convert',
- })->to_json($data);
+ my %headers = @$headers;
+
+ # If they don't specify a type, assume it is a data structure that we
+ # should encode to JSON and change the header accordingly.
+ unless ($headers{'-type'}) {
+ $headers{'-type'} = 'application/json';
+ $data = scalar JSON::DWIW->new({
+ pretty => 1,
+ escape_multi_byte => 1,
+ bad_char_policy => 'convert',
+ })->to_json($data);
+ $q->no_cache(1);
+ binmode STDOUT, ':utf8';
+ }
+
+ print $q->header(%headers);
+ if ($data) {
+ print $data;
+ }
# finish FastCGI if needed and auto-reload ourselves if we were modified
$req->Finish if $running_under_fastcgi;
View
90 lib/Acoustics/Web.pm
@@ -8,6 +8,7 @@ use Time::HiRes 'sleep';
use Moose;
use Module::Load 'load';
use List::Util 'shuffle';
+use LWP::Simple;
has 'acoustics' => (is => 'ro', isa => 'Acoustics');
has 'cgi' => (is => 'ro', isa => 'Object');
@@ -917,5 +918,94 @@ sub stats
return [], $results;
}
+=head2 art
+
+Return album art image for the requested song.
+
+=cut
+
+sub art
+{
+ my $self = shift;
+ my $artist = $self->cgi->param("artist");
+ my $album = $self->cgi->param("album");
+ my $title = $self->cgi->param("title");
+ my $set = $self->cgi->param("set");
+ my $size = 128;
+ if ($self->cgi->param("size")) {
+ $size = int($self->cgi->param("size"));
+ }
+
+ # It's okay if an argument is blank, for the most part.
+ if (!$artist) { $artist = ""; }
+ if (!$album) { $album = ""; }
+ if (!$title) { $title = ""; }
+ my $ret = {};
+
+ # Fix issues with albumless tracks
+ if (length $album < 1) {
+ $album = $title . "__" . $artist;
+ }
+
+ # This is a request to set to a specific URL.
+ if ($set && $set == "yes") {
+ my $url = $self->cgi->param("image");
+ my $image = get $url;
+ INFO("adding art cache for " . $title . ": " . $url);
+ $self->acoustics->query('delete_art_cache', {artist => $artist, album => $album, title => $title});
+ $self->acoustics->query('insert_art_cache', {artist => $artist, album => $album, title => $title, image => $image, url => $url });
+ return [], {};
+ }
+
+ # Check the database first.
+ my @queries = (
+ {artist => $artist, album => $album, title => $title},
+ {artist => $artist, album => $album},
+ {album => $album},
+ );
+
+ my @results;
+
+ # Try some queries until we get results.
+ while (!@results && @queries) {
+ @results = $self->acoustics->query('select_art_cache', shift @queries);
+ }
+
+ # We have a result from the database, dump that and bale.
+ if (@results) {
+ unless (length $results[0]->{image} > 10) {
+ # We found something, but we only have a URL stored for some reason.
+ # This could be because a plugin added it but didn't store the result.
+ $results[0]->{image} = get $results[0]->{url};
+ INFO("updating art cache for " . $results[0]->{title});
+ $self->acoustics->query('delete_art_cache', {artist => $artist, album => $album, title => $title});
+ $self->acoustics->query('insert_art_cache', {artist => $artist, album => $album, title => $title, image => $results[0]->{image}, url => $results[0]->{url} });
+ }
+ return [-type => "image/png"], $results[0]->{image};
+ }
+
+ # XXX: Extension hooks should be added here to
+ # pull from LastFM / Amazon / whatever
+
+ # Fall back to local icon.
+ my $last_try;
+ if ($size < 100) {
+ $last_try = "www-data/icons/cd_big.png";
+ } else {
+ $last_try = "www-data/icons/big_a.png";
+ }
+ open IMAGE, $last_try;
+ local $/;
+ $ret->{image} = <IMAGE>;
+ close IMAGE;
+
+
+ # TODO: We should probably use Perl's GD bindings to resize
+ # the result image to the requested size. I'm not
+ # entirely sure whether I want to, though - klange
+
+ return [-type => "image/png"], $ret->{image};
+}
+
1;
View
40 www-data/acoustics2.css
@@ -919,13 +919,45 @@ a img {
border-top-right-radius: 15px;
}
-#messageBox {
- font-size: 12px;
-}
-
.mini-album-art {
height: 16px;
width: 16px;
vertical-align: middle;
padding-right: 2px;
}
+
+#message-box {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 600px;
+ height: 120px;
+ margin-top: -60px;
+ margin-left: -300px;
+ z-index: 3;
+ padding: 20px;
+ padding-top: 5px;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ -moz-box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
+ -webkit-box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
+ box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
+ border: 1px solid #B62222;
+ background-color: #EC3232;
+ background-color: rgba(236,50,50,0.9);
+ color: #FFF;
+ text-align: center;
+
+ display: none;
+}
+ #message-box div h1 {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ #message-box-close {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ }
+
View
201 www-data/acoustics2.js
@@ -3,6 +3,7 @@ var volume;
var stateTimer;
var templates = {};
var jsonSource = 'json.pl';
+var artSource = 'json.pl?mode=art';
var playingTimer;
var elapsedTime = 0;
var totalTime = 0;
@@ -60,8 +61,47 @@ $(document).ready(function() {
$("#search-results-toggle-right-panel").click(function() { toggleQueueExplicit(); });
insertAdvancedSearch(1);
insertAdvancedSearch(2);
+
+ /* XXX REMOVE THIS IN FINAL RELEASE XXX */
+ /* If they haven't seen it, present users with the
+ * "Welcome to Acoustics Beta" dialog, which appears
+ * in a warning box. */
+
+ function setCookie(name, value, expires) {
+ var exdate = new Date();
+ exdate.setDate(exdate.getDate() + expires);
+ var value = escape(value) + ((expires == null) ? "" : "; expires=" + exdate.toUTCString());
+ document.cookie = name + "=" + value;
+ }
+ function getCookie(name) {
+ var i, x, y, cookies = document.cookie.split(";");
+ for (i = 0; i < cookies.length; i++) {
+ x = cookies[i].substr(0, cookies[i].indexOf("="));
+ y = cookies[i].substr(cookies[i].indexOf("=")+1);
+ x = x.replace(/^\s+|\s+$/g,"");
+ if (x == name) {
+ return unescape(y);
+ }
+ }
+ return null;
+ }
+
+ if (getCookie("_seen_beta") != "yes") {
+ showMessage("Wecome to Acoustics Beta!",
+ "You are using the Beta release of Acoustics 2.0, "+
+ "a massive new release featuring a brand new interface. "+
+ "This release is not finished, and as such may have bugs "+
+ "or general usability issues or missing features.<br /><br />"+
+ "Thank you for taking the time to test the Beta.");
+ setCookie("_seen_beta","yes",10000);
+ }
+
+
+ /* XXX REMOVE THE ABOVE IN FINAL RELEASE XXX */
+
});
+
function insertAdvancedSearch(id) {
var entry = templates.advancedSearchEntry.clone();
if (id == 1) {
@@ -283,7 +323,7 @@ function fillResultTable(json) {
$(".search-results-entry-vote", entry).attr('href',
'javascript:voteSong(' + song.song_id + ')');
- $(".search-results-entry-title a", entry).html(song.title);
+ $(".search-results-entry-title a", entry).html("<img class='mini-album-art' src='" + getAlbumArtUrl(song.artist,song.album,song.title,16) + "' width=16 />" + song.title);
$(".search-results-entry-title a", entry).attr('href',
'#SongDetails/' + song.song_id);
@@ -295,6 +335,8 @@ function fillResultTable(json) {
$(".search-results-entry-artist a", entry).attr('href',
'#SelectRequest/artist/' + uriencode(song.artist));
$("#search-results-table tbody").append(entry);
+ //getLastfmArtImage(song.artist,song.album,song.title,$("#song-art-" + song.song_id));
+
total_length += parseInt(song.length);
}
$("#search-results-table").trigger("update");
@@ -448,12 +490,25 @@ function handlePlayerStateRequest(json) {
}
$("#nothing-playing-info", nowPlayingPanel).remove();
$("#now-playing-info").replaceWith(nowPlayingPanel);
- getLastfmArt();
+ $("#now-playing-album-art").empty();
+ $("#now-playing-album-art").append("<a href='javascript:fixArt(\"" + jsencode(nowPlaying.artist) + "\",\"" +
+ jsencode(nowPlaying.album) + "\",\"" + jsencode(nowPlaying.title) + "\")'>" +
+ "<img id='now-playing-album-art-img' src='" + getAlbumArtUrl(nowPlaying.artist,nowPlaying.album,nowPlaying.title,64) + "' width='64'/></a>");
+ $("#now-playing-album-art-img").reflect({height: 16});
$("#now-playing-progress").progressbar({value: Math.floor(100 * (elapsedTime/totalTime))});
/* Full screen view */
$("#fullscreen-title").html(nowPlaying.title);
$("#fullscreen-artist").html(nowPlaying.artist);
$("#fullscreen-album").html(nowPlaying.album);
+ $("#fullscreen-album-art").empty();
+ $("#fullscreen-album-art").append("<a href='javascript:fixArt(\"" + jsencode(nowPlaying.artist) + "\",\"" +
+ jsencode(nowPlaying.album) + "\",\"" + jsencode(nowPlaying.title) + "\")'>" +
+ "<img id='fullscreen-album-art-img' src='" + getAlbumArtUrl(nowPlaying.artist,nowPlaying.album,nowPlaying.title,300) + "' width='300'/></a>");
+ if (!$.browser.webkit) {
+ $("#fullscreen-album-art-img").reflect({height: 100});
+ }
+ /* And here's the fun part */
+ jQuery.favicon(getAlbumArtUrl(nowPlaying.artist,nowPlaying.album,nowPlaying.title,20));
/* Title Bar */
document.title = nowPlaying.title + " - " + nowPlaying.artist + " [Acoustics]";
} else {
@@ -463,6 +518,7 @@ function handlePlayerStateRequest(json) {
$("#now-playing-panel").replaceWith(nowPlayingPanel);
$("#nothing-playing-info").show();
clearFullscreen();
+ jQuery.favicon("www-data/images/ui2/favicon.ico");
document.title = "Acoustics";
totalTime = -1;
}
@@ -592,9 +648,12 @@ function songDetails(id) {
$("#song-details-file a").attr('title', json.path);
$("#song-details-file a").attr('href',
'#SelectRequest/path/' + uriencode(json.path));
+ $("#song-details-album-art").empty();
+ $("#song-details-album-art").append("<a href='javascript:fixArt(\"" + jsencode(json.artist) + "\",\"" +
+ jsencode(json.album) + "\",\"" + jsencode(json.title) + "\")'>" +
+ "<img id='song-details-album-art-img' src='" + getAlbumArtUrl(json.artist,json.album,json.title,128) + "' width='128'/></a>");
$("#search-results-song-details").slideDown(300, function() {
- //$("#song-details-album-art-img").reflect({height: 32});
- getLastfmArtFloat(json.artist,json.album);
+ $("#song-details-album-art img").reflect({height: 40});
});
if (json.who.length > 0) {
$("#song-details-voters").html(htmlForVoters(json.who));
@@ -621,24 +680,27 @@ function hideSongDetails() {
$("#search-results-song-details").slideUp(300);
}
-$("#messageBox").ready(function() {
- $("#messageBox").dialog({
- autoOpen: false,
- modal: true,
- buttons: {"ok": function() {
- $(this).dialog("close");
- // set the text back to default
- // (so we know if someone forgot to set it in another call)
- $(this).html("no text... why?");
- }}
+$("#message-box").ready(function() {
+ $("#message-box").ajaxError(function (e, xhr, opts, err) {
+ showMessage("Communication Error", xhr.responseText);
});
+});
- $("#messageBox").ajaxError(function (e, xhr, opts, err) {
- $(this).dialog('option', 'title', 'Communication Error');
- $(this).html(xhr.responseText);
- $(this).dialog('open');
+function showMessage(title, message) {
+ $("#message-box-title").empty();
+ $("#message-box-message").empty();
+ $("#message-box-title").html(title);
+ $("#message-box-message").html(message);
+ $("#message-box").show(100, function() {
+ var h = $("#message-box-inner").height() + 10;
+ $("#message-box").height(h);
+ $("#message-box").css("margin-top", (-h / 2) + "px");
});
-});
+}
+
+function closeMessageBox() {
+ $("#message-box").hide(300);
+}
function advancedSearchFormSubmit() {
var conditions = ["OR"];
@@ -679,22 +741,30 @@ function formSearch() {
}
function uriencode(str) {
+ return encodeURIComponent(formencode(str));
+}
+
+function formencode(str) {
str = new String(str);
str = str.replace(/\&/g, '%26');
str = str.replace(/\+/g, '%2b');
str = str.replace(/\#/g, '%23');
str = str.replace(/\//g, '%2f');
- return encodeURIComponent(str);
+ return str;
}
-function formencode(str) {
+function jsencode(str) {
str = new String(str);
- str = str.replace(/\&/g, '%26');
- str = str.replace(/\+/g, '%2b');
- str = str.replace(/\#/g, '%23');
- str = str.replace(/\//g, '%2f');
+ str = str.replace(/\'/g, '&apos;');
+ str = str.replace(/\"/g, '\\\"');
+ return str;
+}
+function moreencode(str) {
+ str = uriencode(str);
+ str = str.replace(/\'/g, '&apos;');
+ str = str.replace(/\"/g, '&quot;');
return str;
}
@@ -748,83 +818,13 @@ function setMenuItem(item) {
$("#header-bar-menu-" + item).addClass("header-bar-menu-selected", 100);
}
-function getLastfmUrl(artist, album) {
- var api_key = "46d779178cb5e43eefe754d0c1c1fecf";
- var url = "http://ws.audioscrobbler.com/2.0/?method=album.getInfo&api_key=" + api_key + "&format=json";
- return url + "&artist=" + uriencode(artist) + "&album=" + uriencode(album);
+function fixArt(artist, album, title) {
+ newArt = prompt("Correct album art for " + title + " by " + artist + ":", "http://example.com/some_image.jpg");
+ $.get(getAlbumArtUrl(artist,album,title,0) + "&set=yes&image=" + newArt);
}
-function getLastfmPreferred(data, size) {
- var path = undefined;
- if (data.album) {
- if (size > 200) {
- for (var i = 0; i < data.album.image.length; i++) {
- if (data.album.image[i].size == "extralarge") {
- path = data.album.image[i]["#text"];
- }
- }
- }
- if (size > 50) {
- if (!path) {
- for (var i = 0; i < data.album.image.length; i++) {
- if (data.album.image[i].size == "large") {
- path = data.album.image[i]["#text"];
- }
- }
- }
- }
- if (size > 30) {
- if (!path) {
- for (var i = 0; i < data.album.image.length; i++) {
- if (data.album.image[i].size == "medium") {
- path = data.album.image[i]["#text"];
- }
- }
- }
- }
- if (!path) {
- path = data.album.image[0]["#text"];
- }
- }
- if (!path) {
- if (size < 128) {
- path = "www-data/icons/cd_big.png";
- } else {
- path = "www-data/icons/big_a.png";
- }
- }
- return path;
-}
-
-function getLastfmArt() {
- if (!nowPlaying) { return; }
- $.getJSON(
- getLastfmUrl(nowPlaying.artist,nowPlaying.album) + "&callback=?",
- function (data) {
- path = getLastfmPreferred(data, 64);
- $("#now-playing-album-art").empty();
- $("#now-playing-album-art").append("<img id='now-playing-album-art-img' src='" + path + "' width='64'/>");
- $("#now-playing-album-art-img").reflect({height: 16});
- path = getLastfmPreferred(data, 300);
- $("#fullscreen-album-art").empty();
- $("#fullscreen-album-art").append("<img id='fullscreen-album-art-img' src='" + path + "' width='300'/>");
- if (!$.browser.webkit) {
- $("#fullscreen-album-art-img").reflect({height: 100});
- }
- }
- );
-}
-
-function getLastfmArtFloat(artist, album) {
- $.getJSON(
- getLastfmUrl(artist,album) + "&callback=?",
- function (data) {
- path = getLastfmPreferred(data, 128);
- $("#song-details-album-art").empty();
- $("#song-details-album-art").append("<img id='song-details-album-art-img' src='" + path + "' width='128'/>");
- $("#song-details-album-art-img").reflect({height: 32});
- }
- );
+function getAlbumArtUrl(artist, album, title, size) {
+ return artSource + "&artist=" + moreencode(artist) + "&album=" + moreencode(album) + "&title=" + moreencode(title) + "&size=" + size
}
function unfullscreen() {
@@ -833,7 +833,6 @@ function unfullscreen() {
function fullscreen() {
$("#fullscreen-view").fadeIn(300, function() {
- getLastfmArt();
});
}
View
156 www-data/jquery.favicon.js
@@ -0,0 +1,156 @@
+/**
+ * jQuery Favicon plugin
+ * http://hellowebapps.com/products/jquery-favicon/
+ *
+ * Copyright (c) 2010 Volodymyr Iatsyshyn (viatsyshyn@hellowebapps.com)
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ *
+ */
+
+(function($){
+
+ var canvas;
+
+ function apply (url) {
+ $('link[rel$=icon]').replaceWith('');
+ $('head').append(
+ $('<link rel="shortcut icon" type="image/x-icon"/>')
+ .attr('href', url));
+ }
+
+ /**
+ * jQuery.favicon
+ *
+ * @param {String} iconURL
+ * @param {String} alternateURL
+ * @param {Function} onDraw
+ *
+ * function (iconURL)
+ * function (iconURL, onDraw)
+ * function (iconURL, alternateURL, onDraw)
+ */
+ $.favicon = function(iconURL, alternateURL, onDraw) {
+
+ if (arguments.length == 2) {
+ // alternateURL is optional
+ onDraw = alternateURL;
+ }
+
+ if (onDraw) {
+ canvas = canvas || $('<canvas />')[0];
+ if (canvas.getContext) {
+ var img = $('<img />')[0];
+ img.onload = function () {
+ $.favicon.unanimate();
+
+ canvas.height = canvas.width = this.width;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(this, 0, 0);
+ onDraw(ctx);
+ apply(canvas.toDataURL('image/png'));
+ };
+ img.src = iconURL;
+ } else {
+ apply(alternateURL || iconURL);
+ }
+ } else {
+ $.favicon.unanimate();
+
+ apply(iconURL);
+ }
+
+ return this;
+ };
+
+ var animation = {
+ timer: null,
+ frames: [],
+ size: 16,
+ count: 1
+ };
+
+ $.extend($.favicon, {
+ /**
+ * jQuery.favicon.animate - starts frames based animation
+ *
+ * @param {String} animationURL Should be image that contains frames joined horizontally
+ * @param {String} alternateURL Normal one frame image that will be used if Canvas is not supported
+ * @param {Object} options optional
+ *
+ * function (animationURL, alternateURL)
+ * function (animationURL, alternateURL, {
+ * interval: 1000, // change frame in X ms, default is 1000ms
+ * onDraw: function (context, frame) {}, // is called each frame
+ * onStop: function () {}, // is called on animation stop
+ * frames: [1,3,5] // display frames in this exact order, defaults is all frames
+ * })
+ */
+ animate: function (animationURL, alternateURL, options) {
+ options = options || {};
+
+ canvas = canvas || $('<canvas />')[0];
+ if (canvas.getContext) {
+ var img = $('<img />')[0];
+ img.onload = function () {
+
+ $.favicon.unanimate();
+
+ animation.onStop = options.onStop;
+
+ animation.image = this;
+ canvas.height = canvas.width = animation.size = this.height;
+ animation.count = this.width / this.height;
+
+ var frames = [];
+ for (var i = 0; i < animation.count; ++i) frames.push(i);
+ animation.frames = options.frames || frames;
+
+ var ctx = canvas.getContext('2d');
+
+ options.onStart && options.onStart();
+ animation.timer = setInterval(function () {
+ // get current frame
+ var frame = animation.frames.shift();
+ animation.frames.push(frame);
+
+ // check if frame exists
+ if (frame >= animation.count) {
+ clearInterval(animation.timer);
+ animation.timer = null;
+
+ throw new Error('jQuery.favicon.animate: frame #' + frame + ' do not exists in "' + animationURL + '"');
+ }
+
+ // draw frame
+ var s = animation.size;
+ ctx.drawImage(animation.image, s * frame, 0, s, s, 0, 0, s, s);
+
+ // User Draw event
+ options.onDraw && options.onDraw(ctx, frame);
+
+ // set favicon
+ apply(canvas.toDataURL('image/png'));
+ }, options.interval || 1000);
+ };
+ img.src = animationURL;
+ } else {
+ apply(alternateURL || animationURL);
+ }
+ },
+
+ /**
+ * jQuery.favicon.unanimate - stops current animation
+ */
+ unanimate: function () {
+ if (animation.timer) {
+ clearInterval(animation.timer);
+ animation.timer = null;
+
+ animation.onStop && animation.onStop();
+ }
+ }
+ })
+})(jQuery);
Please sign in to comment.
Something went wrong with that request. Please try again.