Skip to content

Instantly share code, notes, and snippets.

@bingogg14
Created September 10, 2021 06:28
Show Gist options
  • Save bingogg14/036fed97de801b702f19ff9c7e2db7f2 to your computer and use it in GitHub Desktop.
Save bingogg14/036fed97de801b702f19ff9c7e2db7f2 to your computer and use it in GitHub Desktop.
import $ from "jquery";
window.jQuery = $
window.$ = $
import { Howl } from 'howler';
import axios from "axios";
export function initSoundcloud() {
const SoundCloudClientId = 'f17476445ba4b72bc5760aa679820d27'
// Cache references to DOM elements.
const elmTracks = $('.player[data-track-id]');
const trackIds = [];
elmTracks.each(function () {
const trackId = $(this).data('trackId');
if (typeof trackId !== 'undefined' && trackId !== '') {
if (!trackIds.includes(trackId)) {
trackIds.push(trackId)
}
}
})
if (trackIds.length > 0) {
const trackObjs = [];
trackIds.forEach(function ($value) {
trackObjs.push({
trackId: $value,
howl: null
})
})
// Setup our new audio player class and pass it the playlist.
const Player = function (playlist) {
this.playlist = playlist;
this.trackId = null;
this.changeSeek = false
this.wasAllPaused = false
this.timeout = null
this.timeoutStep = null
}
Player.prototype = {
init: function (trackId) {
const self = this;
trackId = typeof trackId === 'number' ? trackId : self.trackId;
const data = $.grep(self.playlist, function (obj) {return obj.trackId === trackId;})[0];
return new Promise((resolve, reject) => {
if (data.howl !== null) {
resolve(data.howl)
} else {
axios({
method: 'get',
url: 'https://api.soundcloud.com/tracks/'+ trackId +'/streams?format=json&&client_id=' + SoundCloudClientId,
}).then((response) => {
new Promise((resolve) => {
data.howl = new Howl({
src: [response.data['http_mp3_128_url']],
html5: true, // Force to HTML5 so that the audio can stream in (best for large files).
onplay: function() {
// Display the duration.
// Show the pause button.
$('.player[data-track-id="'+ trackId +'"]').addClass('active').addClass('play').removeClass('loading');
// Start upating the progress of the track.
requestAnimationFrame(self.step.bind(self));
},
onload: function () {
// $('.player[data-track-id="'+ trackId +'"]').removeClass('loading');
},
onloaderror: function () {
$('.player[data-track-id="'+ trackId +'"]').removeClass('active').removeClass('play').removeClass('loading');
alert('Oops! failure loading music! Please try again!')
},
onplayerror: function () {
$('.player[data-track-id="'+ trackId +'"]').removeClass('active').removeClass('play').removeClass('loading');
alert('Oops! failure play music! Please try again!')
},
onseek: function () {
},
onpause: function () {
$('.player[data-track-id="'+ trackId +'"]').removeClass('active').removeClass('play');
},
onend: function () {
$('.player[data-track-id="'+ trackId +'"]').removeClass('active').removeClass('play');
}
});
if (data.howl.state() === 'unloaded') {
data.howl.load();
}
resolve(data.howl)
}).then((howl) => {
resolve(howl)
})
}).catch((error) => {
reject(error)
});
}
})
},
/**
* Play a song in the playlist.
* @param {Number} trackId Index of the song in the playlist (leave empty to play the first or current).
*/
play: function(trackId) {
const self = this;
trackId = typeof trackId === 'number' ? trackId : self.trackId;
player.init(trackId).then((howl) => {
const data = $.grep(self.playlist, function (obj) {return obj.trackId === trackId;})[0];
// If we already loaded this track, use the current one.
// Otherwise, setup and load a new Howl.
// Before start play stop all other sounds
this.playlist.forEach(function (value, index) {
if (self.playlist[index].howl !== null && self.playlist[index].trackId !== trackId) {
player.pause(self.playlist[index].trackId);
}
})
if (howl.state() === 'loaded') {
$('.player[data-track-id="'+ trackId +'"]').addClass('active').addClass('play').removeClass('loading');
} else {
//Loading
$('.player[data-track-id="'+ trackId +'"]').removeClass('active').removeClass('play').addClass('loading');
}
// Begin playing the sound.
howl.play();
// Keep track of the index we are currently playing.
self.trackId = trackId;
});
},
/**
* Pause the currently playing track.
*/
pause: function(trackId, seek = false) {
const self = this;
trackId = typeof trackId === 'number' ? trackId : this.trackId;
player.init(trackId).then((howl) => {
howl.pause();
if (seek) {
this.changeSeek = true
}
// Show the play button.
if (this.changeSeek === false) { // Fix with change seek (change icon on play)
$('.player[data-track-id="'+ trackId +'"]').removeClass('active').removeClass('play');
}
});
},
/**
* The step called within requestAnimationFrame to update the playback position.
*/
step: function() {
const self = this;
// Get the Howl we want to manipulate.
const data = $.grep(this.playlist, function (obj) {return obj.trackId === self.trackId;})[0];
const sound = data.howl;
// Determine our current seek position.
const seek = sound.seek() || 0;
$('.player[data-track-id="'+ self.trackId +'"] .progress').css({'width': (((seek / sound.duration()) * 100) || 0) + '%'});
// If the sound is still playing, continue stepping.
if (sound.playing()) {
requestAnimationFrame(self.step.bind(self));
}
},
setSeek: function (trackId, percent) {
this.changeSeek = true;
const self = this;
trackId = typeof trackId === 'number' ? trackId : self.trackId;
self.trackId = trackId;
const data = $.grep(self.playlist, function (obj) {return obj.trackId === trackId;})[0];
if (!self.wasAllPaused) {
player.init(trackId).then()
//Loading
this.playlist.forEach(function (value, index) {
if (self.playlist[index].howl !== null && self.playlist[index].trackId !== trackId) {
player.pause(self.playlist[index].trackId);
}
})
self.wasAllPaused = true;
}
const sound = data.howl;
if (sound !== null) {
if (sound.state() === 'unloaded' || sound.state() === 'loading') {
const el = $('.player[data-track-id="'+ trackId +'"]');
if (!(el.hasClass('loading'))) {
el.addClass('loading');
}
}
if (sound.state() === 'loaded') {
const newSeek = ((sound.duration() / 100) * percent) || 0;
sound.seek(newSeek);
sound.play();
this.changeSeek = false;
this.wasAllPaused = false;
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
} else {
if (this.timeout === null) {
this.timeout = setInterval(function() {
player.setSeek(trackId, percent);
}, 1000)
}
}
} else {
//Force add loading class
const el = $('.player[data-track-id="'+ trackId +'"]');
if (!(el.hasClass('loading'))) {
el.addClass('loading');
}
if (this.timeout === null) {
this.timeout = setInterval(function() {
player.setSeek(trackId, percent);
}, 1000)
}
}
},
/**
* Format the time from seconds to M:SS.
* @param {Number} secs Seconds to format.
* @return {String} Formatted time.
*/
formatTime: function(secs) {
var minutes = Math.floor(secs / 60) || 0;
var seconds = (secs - minutes * 60) || 0;
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
};
//Set Player
const player = new Player(trackObjs);
// Bind our player controls.
$('.player[data-track-id] .control-button').on('click', function() {
const el = $(this).closest('.player[data-track-id]')
if (el.hasClass('active')) {
player.pause(el.data('trackId'));
} else {
player.play(el.data('trackId'));
}
});
var mouseIsDown = {
status: false,
el: {
track: null,
timeline: null,
trackId: null
},
timelinePercent: 0,
};
$('.player[data-track-id] .timeline').on('touchstart mousedown', function(ev) {
// ev.stopPropagation();
// ev.preventDefault();
const self = $(this);
const el = self.closest('.player[data-track-id]');
mouseIsDown.status = true;
mouseIsDown.el.track = el;
mouseIsDown.el.timeline = self;
mouseIsDown.el.progress = self.children().children();
mouseIsDown.el.timelineCoords = this.getBoundingClientRect();
mouseIsDown.el.trackId = el.data('trackId');
player.pause(mouseIsDown.el.trackId);
if (ev.type === 'touchstart') {
setProgressWidth(ev.touches[0].clientX);
} else {
setProgressWidth(ev.clientX);
}
})
$(document).on('touchmove mousemove', function(ev) {
if (ev.type === 'touchmove') {
setProgressWidth(ev.touches[0].clientX);
} else {
setProgressWidth(ev.clientX);
}
});
$(document).on('touchend mouseup', function(ev) {
setPlayerSeek();
});
function setProgressWidth(clientX) {
if (mouseIsDown.status) {
const offsetX = (clientX- mouseIsDown.el.timelineCoords.left);
const percentWidth = 100 * offsetX / mouseIsDown.el.timelineCoords.width;
const roundPercent = percentWidth >= 100 ? 100 : percentWidth <= 0 ? 0 : percentWidth
mouseIsDown.el.progress.css({'width': ((roundPercent) + '%')});
mouseIsDown.timelinePercent = roundPercent;
}
}
function setPlayerSeek() {
if (mouseIsDown.status) {
mouseIsDown.status = false;
player.setSeek(mouseIsDown.el.trackId, mouseIsDown.timelinePercent);
}
}
//Only active track set seek
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment