Skip to content

Instantly share code, notes, and snippets.

@WJWang
Last active October 19, 2018 10:51
Show Gist options
  • Save WJWang/588eaa15dd32ffff86fe6a193eb4a1b0 to your computer and use it in GitHub Desktop.
Save WJWang/588eaa15dd32ffff86fe6a193eb4a1b0 to your computer and use it in GitHub Desktop.
<template lang="html">
<div v-if="processing">
<button disabled>play</button>
<input type="range" v-model="volume" />
</div>
<div v-else>
playing at : {{ playTime }} / {{ songDuration }} secs<br>
Position: <input type="text" v-model="position" /> %
<br>
<button @click="play" v-if="!isPlaying">play</button>
<button @click="pause" v-else>pause</button>
<button @click="replay">replay</button>
Volume: <input type="range" v-model="volume" />
</div>
</template>
<script>
const fetchMediaData = uri => new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', uri, true);
request.responseType = 'arraybuffer';
function onLoad() {
resolve(request.response);
}
function onError(e) {
reject(e);
}
request.onload = onLoad;
request.onerror = onError;
try {
request.send();
} catch (e) {
reject(e);
}
});
const AudioCtx = window.AudioContext || window.webkitAudioContext;
export default {
props: {
url: { type: String, required: true, default: '' },
vol: { type: Number, default: 50 },
pos: { type: Number, default: 0 },
},
data() {
return {
startOffset: 0,
startTime: 0,
playTime: 0,
timer: null,
isPlaying: false,
position: this.pos,
volume: this.vol,
songCtx: null,
songGainNode: null,
songBuffer: null,
song: null,
processing: false,
errorText: '',
};
},
methods: {
volumeAdjust() {
if (this.vol <= 100 && this.volume >= 0) {
this.songGainNode.gain.value = this.volume / 100;
}
if (this.volume > 100) {
this.volume = 100;
this.songGainNode.gain.value = 1;
}
if (this.volume < 0) {
this.volume = 0;
this.songGainNode.gain.value = 0;
}
},
startPlayAtPos() {
this.stop();
this.startOffset = ((this.position) / 100) * this.songDuration;
this.isPlaying = false;
this.play();
},
replay() {
this.stop();
this.startOffset = 0;
this.isPlaying = false;
this.play();
},
stop() {
this.song.stop();
clearInterval(this.timer);
},
play() {
this.songCtx = new AudioCtx();
this.startTime = this.songCtx.currentTime;
this.song = this.songCtx.createBufferSource();
this.song.loop = false;
this.song.buffer = this.songBuffer;
this.songGainNode = this.songCtx.createGain();
this.song.connect(this.songGainNode);
this.songGainNode.connect(this.songCtx.destination);
this.volumeAdjust();
this.song.start(0, this.startOffset % this.songDuration);
this.startTimer();
this.isPlaying = true;
},
pause() {
this.stop();
this.startOffset += this.songCtx.currentTime - this.startTime;
this.isPlaying = false;
},
async fetchSong() {
this.processing = true;
try {
const audioData = await fetchMediaData(this.url);
const setting = {
channels: 2,
frameCounts: 44100 * (60 * 5), // 5 mins as default
sampleRate: 44100,
};
const audioCtx = new AudioCtx();
audioCtx.decodeAudioData(audioData, async (buffer) => {
const frameCounts = (buffer.duration) ? (buffer.duration * 44100) : setting.frameCounts;
const offlineCtx = new OfflineAudioContext(setting.channels, frameCounts, setting.sampleRate);
const source = offlineCtx.createBufferSource();
source.buffer = buffer;
source.connect(offlineCtx.destination);
source.start();
try {
const renderedBuffer = await offlineCtx.startRendering();
this.songBuffer = renderedBuffer;
this.processing = false;
console.log('Rendering completed successfully');
} catch (e) {
console.error(e);
this.processing = false;
this.errorText = 'Rendering failed';
}
});
} catch (e) {
this.processing = false;
this.errorText = 'Media file error occurs.';
}
},
startTimer() {
this.timer = setInterval(() => {
this.playTime = (this.startOffset + (this.songCtx.currentTime - this.startTime)) % this.songDuration;
}, 0);
}
},
computed: {
songDuration() {
if (this.songBuffer) {
return this.songBuffer.duration;
}
return 0;
},
},
created() {
this.fetchSong();
},
watch: {
position() {
this.startPlayAtPos();
},
volume() {
this.volumeAdjust();
}
},
}
</script>
<style lang="sass" scoped>
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment