<body>
<svg display="none" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><symbol id="icon-downlaod" viewBox="0 0 745 1024"><title>downlaod</title><path class="path1" d="M682.545 791.273h-620.364q-6.545 0-11.091-4.545t-4.545-11.091v-93.091q0-6.182 4.545-10.727t11.091-4.545h620.364q6.545 0 11.091 4.545t4.545 10.727v93.091q0 6.545-4.545 11.091t-11.091 4.545zM693.818 290.545l-310.182 310.182q-6.182 4.364-11.273 4.364t-11.273-4.364l-310.182-310.182q-6.182-6.182-3.636-17.455 4-9.455 14.909-9.455h170.545v-201.455q0-6.545 4.545-11.091t11.091-4.545h248q6.545 0 11.091 4.545t4.545 11.091v201.455h170.545q10.909 0 14.909 9.455 2.545 11.273-3.636 17.455z"></path></symbol></defs></svg>
<div class="page-wrap">
<header class="site-header wrapper">
<h1><span class="script">Petersen Family</span>Christmas<br>Songbook</h1>
</header>
<section role="main" class="main wrapper">
<a href="https://soundcloud.com/nicholas_petersen/sets/petersen-family-christmas" class="sc-player">Listen</a>
<a href="http://nicholaspetersen.is/assets/songbook/PetersenFamilyChristmasSongbook.zip" class="button"><svg class="icon-downlaod"><use xlink:href="#icon-downlaod"></use></svg> Download</a>
</section>
<footer>
<p class="small">Copyright ©2014 Nicholas Petersen</p>
<a href="https://codepen.io" class="codepen-logo" style="display:none;">
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg"><symbol id="codepen-logo" viewBox="0 0 120 120"><path class="outer-ring" d="M60.048 0C26.884 0 0 26.9 0 60.048s26.884 60 60 60.047c33.163 0 60.047-26.883 60.047-60.047 S93.211 0 60 0z M60.048 110.233c-27.673 0-50.186-22.514-50.186-50.186S32.375 9.9 60 9.9 c27.672 0 50.2 22.5 50.2 50.186S87.72 110.2 60 110.233z"/><path class="inner-box" d="M97.147 48.319c-0.007-0.047-0.019-0.092-0.026-0.139c-0.016-0.09-0.032-0.18-0.056-0.268 c-0.014-0.053-0.033-0.104-0.05-0.154c-0.025-0.078-0.051-0.156-0.082-0.232c-0.021-0.053-0.047-0.105-0.071-0.156 c-0.033-0.072-0.068-0.143-0.108-0.211c-0.029-0.051-0.061-0.1-0.091-0.148c-0.043-0.066-0.087-0.131-0.135-0.193 c-0.035-0.047-0.072-0.094-0.109-0.139c-0.051-0.059-0.104-0.117-0.159-0.172c-0.042-0.043-0.083-0.086-0.127-0.125 c-0.059-0.053-0.119-0.104-0.181-0.152c-0.048-0.037-0.095-0.074-0.145-0.109c-0.019-0.012-0.035-0.027-0.053-0.039L61.817 23.5 c-1.072-0.715-2.468-0.715-3.54 0L24.34 46.081c-0.018 0.012-0.034 0.027-0.053 0.039c-0.05 0.035-0.097 0.072-0.144 0.1 c-0.062 0.049-0.123 0.1-0.181 0.152c-0.045 0.039-0.086 0.082-0.128 0.125c-0.056 0.055-0.108 0.113-0.158 0.2 c-0.038 0.045-0.075 0.092-0.11 0.139c-0.047 0.062-0.092 0.127-0.134 0.193c-0.032 0.049-0.062 0.098-0.092 0.1 c-0.039 0.068-0.074 0.139-0.108 0.211c-0.024 0.051-0.05 0.104-0.071 0.156c-0.031 0.076-0.057 0.154-0.082 0.2 c-0.017 0.051-0.035 0.102-0.05 0.154c-0.023 0.088-0.039 0.178-0.056 0.268c-0.008 0.047-0.02 0.092-0.025 0.1 c-0.019 0.137-0.029 0.275-0.029 0.416V71.36c0 0.1 0 0.3 0 0.418c0.006 0 0 0.1 0 0.1 c0.017 0.1 0 0.2 0.1 0.268c0.015 0.1 0 0.1 0.1 0.154c0.025 0.1 0.1 0.2 0.1 0.2 c0.021 0.1 0 0.1 0.1 0.154c0.034 0.1 0.1 0.1 0.1 0.213c0.029 0 0.1 0.1 0.1 0.1 c0.042 0.1 0.1 0.1 0.1 0.193c0.035 0 0.1 0.1 0.1 0.139c0.05 0.1 0.1 0.1 0.2 0.2 c0.042 0 0.1 0.1 0.1 0.125c0.058 0.1 0.1 0.1 0.2 0.152c0.047 0 0.1 0.1 0.1 0.1 c0.019 0 0 0 0.1 0.039L58.277 96.64c0.536 0.4 1.2 0.5 1.8 0.537c0.616 0 1.233-0.18 1.77-0.537 l33.938-22.625c0.018-0.012 0.034-0.027 0.053-0.039c0.05-0.035 0.097-0.072 0.145-0.109c0.062-0.049 0.122-0.1 0.181-0.152 c0.044-0.039 0.085-0.082 0.127-0.125c0.056-0.055 0.108-0.113 0.159-0.172c0.037-0.045 0.074-0.09 0.109-0.139 c0.048-0.062 0.092-0.127 0.135-0.193c0.03-0.049 0.062-0.098 0.091-0.146c0.04-0.07 0.075-0.141 0.108-0.213 c0.024-0.051 0.05-0.102 0.071-0.154c0.031-0.078 0.057-0.156 0.082-0.234c0.017-0.051 0.036-0.102 0.05-0.154 c0.023-0.088 0.04-0.178 0.056-0.268c0.008-0.045 0.02-0.092 0.026-0.137c0.018-0.139 0.028-0.277 0.028-0.418V48.735 C97.176 48.6 97.2 48.5 97.1 48.319z M63.238 32.073l25.001 16.666L77.072 56.21l-13.834-9.254V32.073z M56.856 32.1 v14.883L43.023 56.21l-11.168-7.471L56.856 32.073z M29.301 54.708l7.983 5.34l-7.983 5.34V54.708z M56.856 88.022L31.855 71.4 l11.168-7.469l13.833 9.252V88.022z M60.048 67.597l-11.286-7.549l11.286-7.549l11.285 7.549L60.048 67.597z M63.238 88.022V73.14 l13.834-9.252l11.167 7.469L63.238 88.022z M90.794 65.388l-7.982-5.34l7.982-5.34V65.388z"/></symbol></svg>
<svg class="logo">
<use xlink:href="#codepen-logo"></use>
</svg>
</a>
</footer>
</div>
</body>
/* LAYOUT *
-----------------------------------------------*/
* {
box-sizing: border-box;
}
.cf:before,
.cf:after {
content: " "; /* 1 */
display: table; /* 2 */
}
.cf:after {
clear: both;
}
.cf {
*zoom: 1;
}
@small: ~"screen and (min-width: 20em)";
@medium: ~"screen and (min-width: 38em)";
@large: ~"screen and (min-width: 48em)";
@extra-large: ~"screen and (min-width: 58em)";
@max: ~"screen and (min-width: 68em)";
/* COLORS *
-----------------------------------------------*/
@white: #f8f8f8;
@red: #dd4444;
@red-dark: #bf3d3f;
@red-darker: #9c3538;
@black: #1f1f1f;
/* FONTS *
-----------------------------------------------*/
@import url(https://fonts.googleapis.com/css?family=Sacramento|Montserrat);
.script {
font-family: 'Sacramento', cursive;
}
.display {
font-family: 'Montserrat', sans-serif;
}
/* GLOBAL *
-----------------------------------------------*/
body {
margin: 0;
background-color: @red;
background-image: url('http://nicholaspetersen.is/wp-content/themes/v3.1/images/blackbg1-transparent.png');
color: white;
text-align: center;
.display;
@media @medium {
font-size: 112.5%;
}
}
.page-wrap {
width: 100%;
overflow: hidden;
background: url('http://nicholaspetersen.is/wp-content/themes/v3/images/christmas-trees-mobile.png') no-repeat;
background-position: top center;
background-size: 100% auto;
@media @medium {
background: url('http://nicholaspetersen.is/wp-content/themes/v3/images/christmas-trees.png') no-repeat;
background-size: 100% auto;
}
}
.wrapper {
width: 90%;
max-width: 1200px;
margin: 0 auto;
overflow: hidden;
}
p.small {
font-size: 0.8em;
}
a {
color: @red-darker;
&:hover {
color: @red-dark
}
}
a.sc-player,
.button {
display: inline-block;
background: @red-dark;
background: rgba(19, 19, 19, 0.14);
border-radius: 4px;
padding: 1em 1.5em;
text-align: center;
text-decoration: none;
color: @white;
font-size: 1.5em;
.transition;
&:hover {
background: @red-darker;
background: rgba(19, 19, 19, 0.24);
color: @white;
}
}
.button svg {
fill: @white;
width: 30px;
height: 30px;
display: inline-block;
vertical-align: middle;
}
.main {
margin-bottom: 4em;
}
.transition {
transition: all 0.3s ease-out;
}
h1 {
position: relative;
margin-top: 50px;
.display;
text-transform: uppercase;
font-size: 2em;
line-height: 1;
text-shadow: 0.15em 0.1em 3px rgba(19, 19, 19, 0.22);
-webkit-font-smoothing: antialiased;
.transition;
cursor: pointer;
@media @small {
font-size: 2.5em;
}
@media @medium {
font-size: 4.5em;
margin-top: 100px;
}
@media @max {
font-size: 6em;
}
.small {
text-transform: none;
display: block;
.script;
font-weight: normal;
font-size: 0.6em;
position: absolute;
left: 50%;
margin-left: -1.75em;
top: -0.25em;
}
.script {
position: relative;
z-index: 1;
display: block;
font-size: 1.15em;
margin-bottom: -0.15em;
font-weight: normal;
text-transform: none;
}
&:hover {
@media @medium {
transform: translate(-0.1em, -0.1em) scale(1.05);
text-shadow: 0.25em 0.2em 10px rgba(19, 19, 19, 0.22);
}
}
}
/* FOOTER *
-----------------------------------------------*/
footer {
background: @red-dark;
background: rgba(19,19,19,0.14);
padding: 4em;
color: @red-darker;
}
footer p {
text-align: center;
}
.codepen-logo {
display: none;
max-width: 40px;
display: block;
margin: 0 auto;
svg {
width: 40px;
height: 40px;
display: block;
fill: white;
}
}
/* SOUNDCLOUD PLAYER *
-----------------------------------------------*/
.sc-player {
position: relative;
margin-bottom: 2em;
.cf;
a {
text-decoration: none;
color: #fff;
}
ol,
li {
margin: 0;
padding: 0;
list-style-position: inside;
}
}
/* Artworks */
.sc-player .sc-artwork-list{
display: none;
float: left;
width: 40%;
margin-bottom: 3%;
background-color: transparent;
list-style-type: none;
position: relative;
height: 100%;
li {
list-style-type: none;
display: none;
}
li.active{
list-style-type: none;
display: block;
}
}
.sc-player .sc-artwork-list li img,
.sc-player .sc-artwork-list li div {
list-style-type: none;
width: 100%;
height: auto;
}
/* controls */
.sc-player .sc-controls{
display: block;
}
.sc-player .sc-controls a {
text-indent: -9999px;
content: '';
display: block;
background: @red-dark;
background: rgba(19, 19, 19, 0.12);
border-radius: 50%;
width: 150px;
height: 150px;
margin: 0 auto;
position: relative;
.transition;
@media @medium {
width: 220px;
height: 220px;
}
&:hover {
background: @red-darker;
background: rgba(19, 19, 19, 0.22);
}
&:after {
content: '';
display: block;
position: absolute;
top: 50%;
margin-top: -50px;
left: 50%;
margin-left: -50px;
z-index: 1;
//background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/35376/play-pause.png');
background: url('http://nicholaspetersen.is/wp-content/themes/v3/images/play.png');
background-position: top;
background-size: 100px auto;
border-radius: 50%;
width: 100px;
height: 100px;
@media @medium {
width: 180px;
height: 180px;
margin-top: -90px;
margin-left: -90px;
background-size: 180px auto;
}
}
}
.sc-player .sc-controls a.sc-pause:after {
background-position: bottom;
}
.sc-scrubber .sc-time-indicators{
background: @red-darker;
background: rgba(19, 19, 19, 0.22);
color: #fff;
border-radius: 4px;
padding: 7px;
text-align: right;
}
.sc-player .sc-controls a.sc-pause {
display: none;
}
.sc-player.playing .sc-controls a.sc-play {
display: none;
}
.sc-player.playing .sc-controls a.sc-pause {
display: block;
}
/* scrubber */
.sc-scrubber {
position: relative;
float: right;
width: 100%;
margin: 0.5em 0;
border-radius: 4px;
@media @medium {
//margin-top: 20px;
}
.sc-time-span {
height: 100px;
position: relative;
}
.sc-buffer,
.sc-played {
height: 100px;
position: absolute;
top: 0;
}
.sc-time-indicators{
position: absolute;
right: 0;
top: -48px;
}
.sc-time-span {
//background-color: #666;
border-radius: 4px;
overflow: hidden;
}
.sc-volume-slider {
background-color: @red-dark;
background-color: rgba(19,19,19,0.12);
border-radius: 2px;
}
.sc-volume-slider .sc-volume-status{
background-color: @red-darker;
background-color: rgba(19,19,19,0.22);
border-right: 1px solid @red-darker;
border-right: 1px solid rgba(19,19,19,0.22);
//border-right: 1px solid white;
}
.sc-waveform-container {
z-index: 800;
width: 100%;
position: absolute;
}
.sc-time-span img {
height: 100px;
width: 100%;
border-radius: 4px;
}
.sc-buffer {
background: @red-dark;
background: rgba(19, 19, 19, 0.12);
z-index: 1;
position: absolute;
}
.sc-played {
background: @red-darker;
background: rgba(19, 19, 19, 0.22);
z-index: 799;
}
}
/* volume control */
.sc-volume-slider {
top: -35px;
left: 0px;
position: absolute;
width: 150px;
height: 20px;
background-color: white;
.sc-volume-status{
position: absolute;
width: 0%;
height: 20px;
top: 0;
left: 0;
}
}
/* tracks */
/* Track listings*/
.sc-player ol.sc-trackslist {
position: relative;
width: 100%;
overflow: auto;
li {
width: 100%;
cursor: pointer;
margin-bottom: 0.5em;
padding: 4%;
background: @red-dark;
background: rgba(19, 19, 19, 0.12);
color: white;
border-radius: 4px;
transition: background 0.3s ease-in;
text-align: left;
@media @medium {
padding: 2%;
}
&:last-child {
margin-bottom: 0;
}
a {
font-size: 1.125em;
@media @small {
}
@media @medium {
font-size: 2em;
}
}
&:hover {
background: @red-darker;
background: rgba(19, 19, 19, 0.22);
}
&.active{
background: @red-darker;
background: rgba(19, 19, 19, 0.32);
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
a {
color: #fff;
}
}
}
}
.sc-track-duration {
float: right;
margin-top: 0.25em;
@media @medium {
margin-top: 0.5em;
}
}
/* Track info*/
.sc-player .sc-info {
position: relative;
margin-bottom: 2em;
padding: 1% 3%;
@media @medium {
margin-top: 1em;
margin-bottom: 3em;
}
h3 {
font-size: 2em;
margin-bottom: 0.5em;
}
h4 {
display: none;
}
.sc-info a {
color: #fff;
}
}
p {
max-width: 38em;
max-width: 38rem;
margin: 0 auto 1em;
line-height: 1.5;
text-align: left;
}
.sc-player .sc-info-toggle,
.sc-player .sc-info-close {
display: none;
}
/* utilities */
.sc-player .hidden {
//display: none;
}
.sc-player-engine-container{
width: 1px;
height: 1px;
position: fixed;
top: 2px;
left: 2px;
}
.sc-player .sc-info-toggle{
background: #22B573;
color: #fff;
border-radius: 4px;
padding: 4px;
}
.sc-player .sc-info-toggle:hover{
background: #333;
color: #fff;
}
.sc-player .sc-info-close{
background: #22B573;
border-radius: 4px;
padding: 2px 4px;
font-weight: bold;
}
View Compiled
/*
* SoundCloud Custom Player jQuery Plugin
* Author: Matas Petrikas, matas@soundcloud.com
* Copyright (c) 2009 SoundCloud Ltd.
* Licensed under the MIT license:
* https://www.opensource.org/licenses/mit-license.php
*
* Usage:
* <a href="https://soundcloud.com/matas/hobnotropic" class="sc-player">My new dub track</a>
* The link will be automatically replaced by the HTML based player
*/
(function($) {
// Convert milliseconds into Hours (h), Minutes (m), and Seconds (s)
var timecode = function(ms) {
var hms = function(ms) {
return {
h: Math.floor(ms/(60*60*1000)),
m: Math.floor((ms/60000) % 60),
s: Math.floor((ms/1000) % 60)
};
}(ms),
tc = []; // Timecode array to be joined with '.'
if (hms.h > 0) {
tc.push(hms.h);
}
tc.push((hms.m < 10 && hms.h > 0 ? "0" + hms.m : hms.m));
tc.push((hms.s < 10 ? "0" + hms.s : hms.s));
return tc.join('.');
};
// shuffle the array
var shuffle = function(arr) {
arr.sort(function() { return 1 - Math.floor(Math.random() * 3); } );
return arr;
};
var debug = true,
useSandBox = false,
$doc = $(document),
log = function(args) {
try {
if(debug && window.console && window.console.log){
window.console.log.apply(window.console, arguments);
}
} catch (e) {
// no console available
}
},
domain = useSandBox ? 'sandbox-soundcloud.com' : 'soundcloud.com',
secureDocument = (document.location.protocol === 'https:'),
// convert a SoundCloud resource URL to an API URL
scApiUrl = function(url, apiKey) {
var resolver = ( secureDocument || (/^https/i).test(url) ? 'https' : 'http') + '://api.' + domain + '/resolve?url=',
params = 'format=json&consumer_key=' + apiKey +'&callback=?';
// force the secure url in the secure environment
if( secureDocument ) {
url = url.replace(/^http:/, 'https:');
}
// check if it's already a resolved api url
if ( (/api\./).test(url) ) {
return url + '?' + params;
} else {
return resolver + url + '&' + params;
}
};
// TODO Expose the audio engine, so it can be unit-tested
var audioEngine = function() {
var html5AudioAvailable = function() {
var state = false;
try{
var a = new Audio();
state = a.canPlayType && (/maybe|probably/).test(a.canPlayType('audio/mpeg'));
// uncomment the following line, if you want to enable the html5 audio only on mobile devices
// state = state && (/iPad|iphone|mobile|pre\//i).test(navigator.userAgent);
}catch(e){
// there's no audio support here sadly
}
return state;
}(),
callbacks = {
onReady: function() {
$doc.trigger('scPlayer:onAudioReady');
},
onPlay: function() {
$doc.trigger('scPlayer:onMediaPlay');
},
onPause: function() {
$doc.trigger('scPlayer:onMediaPause');
},
onEnd: function() {
$doc.trigger('scPlayer:onMediaEnd');
},
onBuffer: function(percent) {
$doc.trigger({type: 'scPlayer:onMediaBuffering', percent: percent});
}
};
var html5Driver = function() {
var player = new Audio(),
onTimeUpdate = function(event){
var obj = event.target,
buffer = ((obj.buffered.length && obj.buffered.end(0)) / obj.duration) * 100;
// ipad has no progress events implemented yet
callbacks.onBuffer(buffer);
// anounce if it's finished for the clients without 'ended' events implementation
if (obj.currentTime === obj.duration) { callbacks.onEnd(); }
},
onProgress = function(event) {
var obj = event.target,
buffer = ((obj.buffered.length && obj.buffered.end(0)) / obj.duration) * 100;
callbacks.onBuffer(buffer);
};
$('<div class="sc-player-engine-container"></div>').appendTo(document.body).append(player);
// prepare the listeners
player.addEventListener('play', callbacks.onPlay, false);
player.addEventListener('pause', callbacks.onPause, false);
// handled in the onTimeUpdate for now untill all the browsers support 'ended' event
// player.addEventListener('ended', callbacks.onEnd, false);
player.addEventListener('timeupdate', onTimeUpdate, false);
player.addEventListener('progress', onProgress, false);
return {
load: function(track, apiKey) {
player.pause();
player.src = track.stream_url + (/\?/.test(track.stream_url) ? '&' : '?') + 'consumer_key=' + apiKey;
player.load();
player.play();
},
play: function() {
player.play();
},
pause: function() {
player.pause();
},
stop: function(){
if (player.currentTime) {
player.currentTime = 0;
player.pause();
}
},
seek: function(relative){
player.currentTime = player.duration * relative;
player.play();
},
getDuration: function() {
return player.duration * 1000;
},
getPosition: function() {
return player.currentTime * 1000;
},
setVolume: function(val) {
player.volume = val / 100;
}
};
};
var flashDriver = function() {
var engineId = 'scPlayerEngine',
player,
flashHtml = function(url) {
var swf = (secureDocument ? 'https' : 'http') + '://player.' + domain +'/player.swf?url=' + url +'&enable_api=true&player_type=engine&object_id=' + engineId;
if ($.browser.msie) {
return '<object height="100%" width="100%" id="' + engineId + '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" data="' + swf + '">'+
'<param name="movie" value="' + swf + '" />'+
'<param name="allowscriptaccess" value="always" />'+
'</object>';
} else {
return '<object height="100%" width="100%" id="' + engineId + '">'+
'<embed allowscriptaccess="always" height="100%" width="100%" src="' + swf + '" type="application/x-shockwave-flash" name="' + engineId + '" />'+
'</object>';
}
};
// listen to audio engine events
// when the loaded track is ready to play
soundcloud.addEventListener('onPlayerReady', function(flashId, data) {
player = soundcloud.getPlayer(engineId);
callbacks.onReady();
});
// when the loaded track finished playing
soundcloud.addEventListener('onMediaEnd', callbacks.onEnd);
// when the loaded track is still buffering
soundcloud.addEventListener('onMediaBuffering', function(flashId, data) {
callbacks.onBuffer(data.percent);
});
// when the loaded track started to play
soundcloud.addEventListener('onMediaPlay', callbacks.onPlay);
// when the loaded track is was paused
soundcloud.addEventListener('onMediaPause', callbacks.onPause);
return {
load: function(track) {
var url = track.uri;
if(player){
player.api_load(url);
}else{
// create a container for the flash engine (IE needs this to operate properly)
$('<div class="sc-player-engine-container"></div>').appendTo(document.body).html(flashHtml(url));
}
},
play: function() {
player && player.api_play();
},
pause: function() {
player && player.api_pause();
},
stop: function(){
player && player.api_stop();
},
seek: function(relative){
player && player.api_seekTo((player.api_getTrackDuration() * relative));
},
getDuration: function() {
return player && player.api_getTrackDuration && player.api_getTrackDuration() * 1000;
},
getPosition: function() {
return player && player.api_getTrackPosition && player.api_getTrackPosition() * 1000;
},
setVolume: function(val) {
if(player && player.api_setVolume){
player.api_setVolume(val);
}
}
};
};
return html5AudioAvailable? html5Driver() : flashDriver();
}();
var apiKey,
didAutoPlay = false,
players = [],
updates = {},
currentUrl,
loadTracksData = function($player, links, key) {
var index = 0,
playerObj = {node: $player, tracks: []},
loadUrl = function(link) {
var apiUrl = scApiUrl(link.url, apiKey);
$.getJSON(apiUrl, function(data) {
// log('data loaded', link.url, data);
index += 1;
if(data.tracks){
// log('data.tracks', data.tracks);
playerObj.tracks = playerObj.tracks.concat(data.tracks);
}else if(data.duration){
// a secret link fix, till the SC API returns permalink with secret on secret response
data.permalink_url = link.url;
// if track, add to player
playerObj.tracks.push(data);
}else if(data.creator){
// it's a group!
links.push({url:data.uri + '/tracks'});
}else if(data.username){
// if user, get his tracks or favorites
if(/favorites/.test(link.url)){
links.push({url:data.uri + '/favorites'});
}else{
links.push({url:data.uri + '/tracks'});
}
}else if($.isArray(data)){
playerObj.tracks = playerObj.tracks.concat(data);
}
if(links[index]){
// if there are more track to load, get them from the api
loadUrl(links[index]);
}else{
// if loading finishes, anounce it to the GUI
playerObj.node.trigger({type:'onTrackDataLoaded', playerObj: playerObj, url: apiUrl});
}
});
};
// update current API key
apiKey = key;
// update the players queue
players.push(playerObj);
// load first tracks
loadUrl(links[index]);
},
artworkImage = function(track, usePlaceholder) {
if(usePlaceholder){
return '<div class="sc-loading-artwork">Loading Artwork</div>';
}else if (track.artwork_url) {
return '<img src="' + track.artwork_url.replace('-large', '-t300x300') + '"/>';
}else{
return '<div class="sc-no-artwork">No Artwork</div>';
}
},
updateTrackInfo = function($player, track) {
// update the current track info in the player
// log('updateTrackInfo', track);
$('.sc-info', $player).each(function(index) {
$('h3', this).html('<a href="' + track.permalink_url +'">' + track.title + '</a>');
$('h4', this).html('by <a href="' + track.user.permalink_url +'">' + track.user.username + '</a>');
$('p', this).html(track.description || 'no Description');
});
// update the artwork
$('.sc-artwork-list li', $player).each(function(index) {
var $item = $(this),
itemTrack = $item.data('sc-track');
if (itemTrack === track) {
// show track artwork
$item
.addClass('active')
.find('.sc-loading-artwork')
.each(function(index) {
// if the image isn't loaded yet, do it now
$(this).removeClass('sc-loading-artwork').html(artworkImage(track, false));
});
}else{
// reset other artworks
$item.removeClass('active');
}
});
// update the track duration in the progress bar
$('.sc-duration', $player).html(timecode(track.duration));
// put the waveform into the progress bar
$('.sc-waveform-container', $player).html('<img src="' + track.waveform_url +'" />');
$player.trigger('onPlayerTrackSwitch.scPlayer', [track]);
},
play = function(track) {
var url = track.permalink_url;
if(currentUrl === url){
// log('will play');
audioEngine.play();
}else{
currentUrl = url;
// log('will load', url);
audioEngine.load(track, apiKey);
}
},
getPlayerData = function(node) {
return players[$(node).data('sc-player').id];
},
updatePlayStatus = function(player, status) {
if(status){
// reset all other players playing status
$('div.sc-player.playing').removeClass('playing');
}
$(player)
.toggleClass('playing', status)
.trigger((status ? 'onPlayerPlay' : 'onPlayerPause'));
},
onPlay = function(player, id) {
var track = getPlayerData(player).tracks[id || 0];
updateTrackInfo(player, track);
// cache the references to most updated DOM nodes in the progress bar
updates = {
$buffer: $('.sc-buffer', player),
$played: $('.sc-played', player),
position: $('.sc-position', player)[0]
};
updatePlayStatus(player, true);
play(track);
},
onPause = function(player) {
updatePlayStatus(player, false);
audioEngine.pause();
},
onFinish = function() {
var $player = updates.$played.closest('.sc-player'),
$nextItem;
// update the scrubber width
updates.$played.css('width', '0%');
// show the position in the track position counter
updates.position.innerHTML = timecode(0);
// reset the player state
updatePlayStatus($player, false);
// stop the audio
audioEngine.stop();
$player.trigger('onPlayerTrackFinish');
},
onSeek = function(player, relative) {
audioEngine.seek(relative);
$(player).trigger('onPlayerSeek');
},
onSkip = function(player) {
var $player = $(player);
// continue playing through all players
log('track finished get the next one');
$nextItem = $('.sc-trackslist li.active', $player).next('li');
// try to find the next track in other player
if(!$nextItem.length){
$nextItem = $player.nextAll('div.sc-player:first').find('.sc-trackslist li.active');
}
$nextItem.click();
},
soundVolume = function() {
var vol = 80,
cooks = document.cookie.split(';'),
volRx = new RegExp('scPlayer_volume=(\\d+)');
for(var i in cooks){
if(volRx.test(cooks[i])){
vol = parseInt(cooks[i].match(volRx)[1], 10);
break;
}
}
return vol;
}(),
onVolume = function(volume) {
var vol = Math.floor(volume);
// save the volume in the cookie
var date = new Date();
date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
soundVolume = vol;
document.cookie = ['scPlayer_volume=', vol, '; expires=', date.toUTCString(), '; path="/"'].join('');
// update the volume in the engine
audioEngine.setVolume(soundVolume);
},
positionPoll;
// listen to audio engine events
$doc
.bind('scPlayer:onAudioReady', function(event) {
log('onPlayerReady: audio engine is ready');
audioEngine.play();
// set initial volume
onVolume(soundVolume);
})
// when the loaded track started to play
.bind('scPlayer:onMediaPlay', function(event) {
clearInterval(positionPoll);
positionPoll = setInterval(function() {
var duration = audioEngine.getDuration(),
position = audioEngine.getPosition(),
relative = (position / duration);
// update the scrubber width
updates.$played.css('width', (100 * relative) + '%');
// show the position in the track position counter
updates.position.innerHTML = timecode(position);
// announce the track position to the DOM
$doc.trigger({
type: 'onMediaTimeUpdate.scPlayer',
duration: duration,
position: position,
relative: relative
});
}, 500);
})
// when the loaded track is was paused
.bind('scPlayer:onMediaPause', function(event) {
clearInterval(positionPoll);
positionPoll = null;
})
// change the volume
.bind('scPlayer:onVolumeChange', function(event) {
onVolume(event.volume);
})
.bind('scPlayer:onMediaEnd', function(event) {
onFinish();
})
.bind('scPlayer:onMediaBuffering', function(event) {
updates.$buffer.css('width', event.percent + '%');
});
// Generate custom skinnable HTML/CSS/JavaScript based SoundCloud players from links to SoundCloud resources
$.scPlayer = function(options, node) {
var opts = $.extend({}, $.scPlayer.defaults, options),
playerId = players.length,
$source = node && $(node),
sourceClasses = $source[0].className.replace('sc-player', ''),
links = opts.links || $.map($('a', $source).add($source.filter('a')), function(val) { return {url: val.href, title: val.innerHTML}; }),
$player = $('<div class="sc-player loading"></div>').data('sc-player', {id: playerId}),
$artworks = $('<ol class="sc-artwork-list"></ol>').appendTo($player),
$controls = $('<div class="sc-controls"></div>').appendTo($player),
$info = $('<div class="sc-info"><h3></h3><h4></h4><p></p><a href="#" class="sc-info-close">X</a></div>').appendTo($player);
// add the classes of the source node to the player itself
// the players can be indvidually styled this way
if(sourceClasses || opts.customClass){
$player.addClass(sourceClasses).addClass(opts.customClass);
}
// adding controls to the player
$player
.find('.sc-controls')
.append('<a href="#play" class="sc-play">Play</a> <a href="#pause" class="sc-pause hidden">Pause</a>')
.end()
.append('<a href="#info" class="sc-info-toggle">Info</a>')
.append('<div class="sc-scrubber"></div>')
.find('.sc-scrubber')
.append('<div class="sc-volume-slider"><span class="sc-volume-status" style="width:' + soundVolume +'%"></span></div>')
.append('<div class="sc-time-span"><div class="sc-waveform-container"></div><div class="sc-buffer"></div><div class="sc-played"></div></div>')
.append('<div class="sc-time-indicators"><span class="sc-position"></span> | <span class="sc-duration"></span></div>');
$list = $('<ol class="sc-trackslist"></ol>').appendTo($player);
// load and parse the track data from SoundCloud API
loadTracksData($player, links, opts.apiKey);
// init the player GUI, when the tracks data was laoded
$player.bind('onTrackDataLoaded.scPlayer', function(event) {
// log('onTrackDataLoaded.scPlayer', event.playerObj, playerId, event.target);
var tracks = event.playerObj.tracks;
if (opts.randomize) {
tracks = shuffle(tracks);
}
// create the playlist
$.each(tracks, function(index, track) {
var active = index === 0;
// create an item in the playlist
$('<li><a href="' + track.permalink_url +'">' + track.title + '</a><span class="sc-track-duration">' + timecode(track.duration) + '</span></li>').data('sc-track', {id:index}).toggleClass('active', active).appendTo($list);
// create an item in the artwork list
$('<li></li>')
.append(artworkImage(track, index >= opts.loadArtworks))
.appendTo($artworks)
.toggleClass('active', active)
.data('sc-track', track);
});
// update the element before rendering it in the DOM
$player.each(function() {
if($.isFunction(opts.beforeRender)){
opts.beforeRender.call(this, tracks);
}
});
// set the first track's duration
$('.sc-duration', $player)[0].innerHTML = timecode(tracks[0].duration);
$('.sc-position', $player)[0].innerHTML = timecode(0);
// set up the first track info
updateTrackInfo($player, tracks[0]);
// if continous play enabled always skip to the next track after one finishes
if (opts.continuePlayback) {
$player.bind('onPlayerTrackFinish', function(event) {
onSkip($player);
});
}
// announce the succesful initialization
$player
.removeClass('loading')
.trigger('onPlayerInit');
// if auto play is enabled and it's the first player, start playing
if(opts.autoPlay && !didAutoPlay){
onPlay($player);
didAutoPlay = true;
}
});
// replace the DOM source (if there's one)
$source.each(function(index) {
$(this).replaceWith($player);
});
return $player;
};
// stop all players, might be useful, before replacing the player dynamically
$.scPlayer.stopAll = function() {
$('.sc-player.playing a.sc-pause').click();
};
// destroy all the players and audio engine, usefull when reloading part of the page and audio has to stop
$.scPlayer.destroy = function() {
$('.sc-player, .sc-player-engine-container').remove();
};
// plugin wrapper
$.fn.scPlayer = function(options) {
// reset the auto play
didAutoPlay = false;
// create the players
this.each(function() {
$.scPlayer(options, this);
});
return this;
};
// default plugin options
$.scPlayer.defaults = $.fn.scPlayer.defaults = {
customClass: null,
// do something with the dom object before you render it, add nodes, get more data from the services etc.
beforeRender : function(tracksData) {
var $player = $(this);
},
// initialization, when dom is ready
onDomReady : function() {
$('a.sc-player, div.sc-player').scPlayer();
},
autoPlay: false,
continuePlayback: true,
randomize: false,
loadArtworks: 5,
// the default Api key should be replaced by your own one
// get it here https://soundcloud.com/you/apps/new
apiKey: 'htuiRd1JP11Ww0X72T1C3g'
};
// the GUI event bindings
//--------------------------------------------------------
// toggling play/pause
$(document).on('click','a.sc-play, a.sc-pause', function(event) {
var $list = $(this).closest('.sc-player').find('ol.sc-trackslist');
// simulate the click in the tracklist
$list.find('li.active').click();
return false;
});
// displaying the info panel in the player
$(document).on('click','a.sc-info-toggle, a.sc-info-close', function(event) {
var $link = $(this);
$link.closest('.sc-player')
.find('.sc-info').toggleClass('active').end()
.find('a.sc-info-toggle').toggleClass('active');
return false;
});
// selecting tracks in the playlist
$(document).on('click','.sc-trackslist li', function(event) {
var $track = $(this),
$player = $track.closest('.sc-player'),
trackId = $track.data('sc-track').id,
play = $player.is(':not(.playing)') || $track.is(':not(.active)');
if (play) {
onPlay($player, trackId);
}else{
onPause($player);
}
$track.addClass('active').siblings('li').removeClass('active');
$('.artworks li', $player).each(function(index) {
$(this).toggleClass('active', index === trackId);
});
return false;
});
var scrub = function(node, xPos) {
var $scrubber = $(node).closest('.sc-time-span'),
$buffer = $scrubber.find('.sc-buffer'),
$available = $scrubber.find('.sc-waveform-container img'),
$player = $scrubber.closest('.sc-player'),
relative = Math.min($buffer.width(), (xPos - $available.offset().left)) / $available.width();
onSeek($player, relative);
};
var onTouchMove = function(ev) {
if (ev.targetTouches.length === 1) {
scrub(ev.target, ev.targetTouches && ev.targetTouches.length && ev.targetTouches[0].clientX);
ev.preventDefault();
}
};
// seeking in the loaded track buffer
$(document)
.on('click','.sc-time-span', function(event) {
scrub(this, event.pageX);
return false;
})
.on('touchstart','.sc-time-span', function(event) {
this.addEventListener('touchmove', onTouchMove, false);
event.originalEvent.preventDefault();
})
.on('touchend','.sc-time-span', function(event) {
this.removeEventListener('touchmove', onTouchMove, false);
event.originalEvent.preventDefault();
});
// changing volume in the player
var startVolumeTracking = function(node, startEvent) {
var $node = $(node),
originX = $node.offset().left,
originWidth = $node.width(),
getVolume = function(x) {
return Math.floor(((x - originX)/originWidth)*100);
},
update = function(event) {
$doc.trigger({type: 'scPlayer:onVolumeChange', volume: getVolume(event.pageX)});
};
$node.bind('mousemove.sc-player', update);
update(startEvent);
};
var stopVolumeTracking = function(node, event) {
$(node).unbind('mousemove.sc-player');
};
$(document)
.on('mousedown','.sc-volume-slider', function(event) {
startVolumeTracking(this, event);
})
.on('mouseup','.sc-volume-slider', function(event) {
stopVolumeTracking(this, event);
});
$doc.bind('scPlayer:onVolumeChange', function(event) {
$('span.sc-volume-status').css({width: event.volume + '%'});
});
// -------------------------------------------------------------------
// the default Auto-Initialization
$(function() {
if($.isFunction($.scPlayer.defaults.onDomReady)){
$.scPlayer.defaults.onDomReady();
}
});
})(jQuery);
This Pen doesn't use any external CSS resources.