JamVueSSRPOC/src/js/components/RecordPlayer.vue
2019-08-16 22:07:28 +01:00

461 lines
12 KiB
Vue

<template lang="html">
<div class="RecordPlayer" v-bind:data-record="recordUrl">
<button class="toggle" v-on:click="togglePlay">
<svg v-if="playing === false">
<use :xlink:href="playIcon" />
</svg>
<svg v-else>
<use :xlink:href="pauseIcon" />
</svg>
</button>
<canvas
class="disc"
ref="disc"
v-bind:width="canvasSize"
v-bind:height="canvasSize"
v-on:mousedown="beginScrubbing"
/>
<!-- <p class="devinfo">Scrubbing: {{ scrubbing }}</p> -->
</div>
</template>
<script>
export default {
props: ["record"],
data: function(args) {
let maxRecordSize = 320 - 40;
let maxRecordInnerSize = 100;
return {
playing: false,
scrubbing: false,
playIcon: "#playbutton",
pauseIcon: "#pausebutton",
playPosition: 0,
buffered: 0,
mousePos: null,
initialised: false,
bassBoom: 1,
canvasSize: 320,
maxRecordSize,
minRecordSize: maxRecordSize - maxRecordSize / 6,
maxRecordInnerSize,
recordInnerSize: maxRecordInnerSize,
recordSize: maxRecordSize,
eqBarCache: null,
eqBarWidth: 0,
eqBarRotation: 0,
eqSampleSize: 2,
recordUrl: this.record
};
},
mounted: function() {
this.audioElement = document.createElement("audio");
this.audioElement.crossOrigin = "anonymous";
this.audioElement.src = this.record;
let disc = this.$refs.disc;
this.eqBarCache = document.createElement("canvas");
this.eqBarCacheWhite = document.createElement("canvas");
// this.eqBarCache = this.$refs.cache;
this.eqBarCache.width = 80;
this.eqBarCache.height = disc.height / 2;
this.eqBarCacheWhite.width = 80;
this.eqBarCacheWhite.height = disc.height / 2;
this.discCtx = disc.getContext("2d");
this.drawUI();
this.audioElement.addEventListener("playing", () => {
if (!this.initialised) {
this.initialised = true;
}
if (!this.analyser) {
this.audioCtx = new (window.AudioContext ||
window.webkitAudioContext)();
this.analyser = this.audioCtx.createAnalyser();
this.source = this.audioCtx.createMediaStreamSource(
this.audioElement.captureStream()
);
this.source.connect(this.analyser);
this.analyser.fftSize = 256;
this.analyser.smoothingTimeConstant = 0.3;
this.bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(this.bufferLength);
this.freqArray = new Uint8Array(this.bufferLength);
let segmentWidth =
360 / (this.analyser.fftSize / (this.eqSampleSize * 2));
this.eqBarWidth = (segmentWidth / 5) * 3 * (Math.PI / 180);
this.eqBarRotation = segmentWidth * (Math.PI / 180);
this.createBarData();
} else {
// let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
this.source = this.audioCtx.createMediaStreamSource(
this.audioElement.captureStream()
);
this.source.connect(this.analyser);
}
});
this.audioElement.addEventListener("ended", () => {
this.playing = false;
this.initialised = false;
this.drawUI();
});
},
methods: {
createBarData: function() {
this.drawBar(this.eqBarCache, "rgb(0, 0, 0)");
this.drawBar(this.eqBarCacheWhite, "rgb(255, 255, 255)");
},
drawBar: function(canvas, colour) {
let ctx = canvas.getContext("2d");
let disc = this.$refs.disc;
ctx.fillStyle = "rgba(0, 0, 0, 0.0)";
ctx.clearRect(0, 0, ctx.width, ctx.height);
ctx.fillStyle = colour;
ctx.beginPath();
ctx.moveTo(0, this.eqBarCache.height);
ctx.arc(
0,
disc.height / 2,
disc.height / 2,
0 - Math.PI / 2,
this.eqBarWidth - Math.PI / 2
// Math.PI * 2
);
// ctx.moveTo(0, this.eqBarCache.height);
ctx.closePath();
ctx.fill();
},
draw: function() {
if (this.playing || this.scrubbing) {
let drawVisual = requestAnimationFrame(this.draw);
}
let disc = this.$refs.disc;
let ctx = this.discCtx;
ctx.clearRect(0, 0, disc.width, disc.height);
this.drawUI();
if (this.analyser) {
this.runAnalysis();
}
},
drawUI: function() {
let ctx = this.discCtx;
let disc = this.$refs.disc;
// Outer ring
ctx.lineWidth = 3;
ctx.beginPath();
ctx.strokeStyle = "rgb(38, 38, 38)";
ctx.arc(
disc.width / 2,
disc.height / 2,
disc.width / 2 - 4,
0,
Math.PI * 2
);
ctx.closePath();
ctx.stroke();
// Inner Ring
ctx.beginPath();
ctx.arc(disc.width / 2, disc.height / 2, 80 / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
if (this.initialised) {
this.drawRecord();
}
},
runAnalysis: function() {
let ctx = this.discCtx;
let disc = this.$refs.disc;
this.analyser.getByteFrequencyData(this.dataArray);
this.analyser.getByteTimeDomainData(this.freqArray);
let bassSample = this.dataArray.slice(this.dataArray.length / 4);
let bassVolume =
bassSample.reduce((accumulated, vol) => {
return accumulated + vol;
}, 0) /
(this.dataArray.length / 4);
this.bassBoom = bassVolume / 255;
let variance = this.bassBoom * (this.maxRecordSize - this.minRecordSize);
this.recordSize = this.minRecordSize + variance;
this.recordInnerSize = this.maxRecordInnerSize + variance;
},
drawRecord: function() {
let ctx = this.discCtx;
let disc = this.$refs.disc;
// White Disc
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.beginPath();
ctx.arc(
disc.width / 2,
disc.height / 2,
this.recordSize / 2,
0,
Math.PI * 2,
false
);
ctx.arc(
disc.width / 2,
disc.height / 2,
this.recordInnerSize / 2,
0,
Math.PI * 2,
true
);
ctx.closePath();
ctx.fill();
if (this.audioElement.buffered.length > 0) {
let buffered = this.audioElement.buffered;
this.buffered =
(this.audioElement.buffered.end(
this.audioElement.buffered.length - 1
) /
this.audioElement.duration) *
100;
}
if (this.audioElement.duration) {
this.playPosition =
(this.audioElement.currentTime / this.audioElement.duration) * 100;
}
// Buffered Ring
this.drawProgressBar("rgb(200, 200, 200)", this.buffered);
// Playhead Position
this.drawProgressBar("rgb(0, 0, 0)", this.playPosition);
this.drawEqualizerBars();
},
drawProgressBar: function(color, percentage) {
let ctx = this.discCtx;
let disc = this.$refs.disc;
let startPos = Math.PI * 2 - Math.PI / 2;
let progressPosition = startPos + ((Math.PI * 2) / 100) * percentage;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(
disc.width / 2,
disc.height / 2,
this.recordSize / 2,
progressPosition,
startPos,
true
);
ctx.lineTo(disc.width / 2, disc.height / 2 - 11 / 2);
ctx.arc(
disc.width / 2,
disc.height / 2,
this.recordInnerSize / 2,
startPos,
progressPosition,
false
);
ctx.closePath();
ctx.fill();
},
drawEqualizerBars() {
let ctx = this.discCtx;
let disc = this.$refs.disc;
let dataPoints = [];
let rotateAmount = this.eqBarRotation;
// console.log(this.freqArray.length / this.eqSampleSize);
for (let i = 0; i < this.freqArray.length; i += this.eqSampleSize) {
dataPoints.push(this.freqArray.slice(i, i + this.eqSampleSize));
}
dataPoints.forEach((freqData, i) => {
let mean =
freqData.reduce((dataPoint, total) => {
return total + dataPoint;
}, 0) / freqData.length;
let multiplier = (mean - 126) / 100;
let ringWidth = this.recordSize - this.recordInnerSize;
let barSize = (this.recordInnerSize + ringWidth * multiplier) / 2;
barSize = barSize >= 0 ? barSize : 0;
// if (i === 0) {
// console.log(barSize, this.recordInnerSize);
// }
ctx.save();
ctx.beginPath();
// if (barSize < this.recordInnerSize / 2) {
ctx.arc(
disc.width / 2,
disc.height / 2,
this.recordInnerSize / 2,
0,
Math.PI * 2,
barSize <= this.recordInnerSize / 2
);
// }
// if (i === 3) {
// console.log(
// this.playPosition,
// i / dataPoints.length * 100,
// this.playPosition < i / dataPoints.length * 100
// );
// }
ctx.arc(
disc.width / 2,
disc.height / 2,
barSize,
0,
Math.PI * 2,
barSize > this.recordInnerSize / 2
);
ctx.closePath();
ctx.translate(disc.width / 2, disc.height / 2);
ctx.rotate(rotateAmount * i);
ctx.clip();
ctx.drawImage(
barSize > this.recordInnerSize / 2 &&
this.playPosition < (i / dataPoints.length) * 100
? this.eqBarCache
: this.eqBarCacheWhite,
/*disc.width / 2*/ 0,
-(disc.height / 2)
);
ctx.rotate(-(rotateAmount * i));
ctx.translate(-(disc.width / 2), -(disc.height / 2));
ctx.restore();
});
},
togglePlay: function() {
if (!this.playing) {
this.audioElement.play();
this.playing = !this.playing;
this.draw();
} else {
this.audioElement.pause();
this.playing = !this.playing;
}
},
beginScrubbing: function(e) {
if (this.initialised) {
this.scrubbing = true;
this.draw();
window.addEventListener("mouseup", this.finishScrubbing);
window.addEventListener("mousemove", this.scrub);
this.scrub(e);
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
e.cancelBubble = true;
e.returnValue = false;
return false;
}
},
scrub: function(e) {
let discPos = this.$refs.disc.getBoundingClientRect();
let centre = {
x: discPos.x + discPos.width / 2,
y: discPos.y + discPos.height / 2
};
let mousePos = { x: e.clientX, y: e.clientY };
let angle =
Math.atan2(centre.y - mousePos.y, centre.x - mousePos.x) +
Math.PI +
Math.PI / 2;
angle = angle > Math.PI * 2 ? angle - Math.PI * 2 : angle;
let position = angle / (Math.PI * 2);
this.audioElement.currentTime = this.audioElement.duration * position;
},
finishScrubbing: function(e) {
this.scrubbing = false;
window.removeEventListener("mouseup", this.finishScrubbing);
window.removeEventListener("mousemove", this.scrub);
}
}
};
</script>
<style lang="scss" scoped>
// @import "~styles/_vars.scss";
.RecordPlayer {
position: relative;
width: 160px;
height: 160px;
}
.disc {
width: 160px;
height: 160px;
&:hover {
cursor: pointer;
}
}
.toggle {
position: absolute;
width: 38px;
height: 38px;
top: 61px;
left: 61px;
border: 0;
background: none;
border-radius: 50%;
background: linear-gradient(#191919, #000);
&:focus {
outline: none;
}
&:hover {
cursor: pointer;
background: linear-gradient(#000, #191919);
}
svg {
fill: white;
width: 10px;
height: 10px;
display: block;
margin: auto;
}
}
.devinfo {
position: absolute;
top: 100%;
width: 100%;
display: block;
text-align: center;
}
</style>