Skip to content

Instantly share code, notes, and snippets.

@jaames
Created May 5, 2017 17:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaames/cc00a3495f8f1efb95b47d2f6663b624 to your computer and use it in GitHub Desktop.
Save jaames/cc00a3495f8f1efb95b47d2f6663b624 to your computer and use it in GitHub Desktop.
sudomemo video player component, for Svelte (https://svelte.technology/guide)
<div class="player">
<div class="player__stage" ref:player on:click="togglePlay()">
<video
class="player__video"
src="{{src}}"
loop="{{meta.loop}}"
ref:video
on:play="_video_play()"
on:pause="_video_pause()"
on:timeupdate="_video_timeupdate()"
></video>
</div>
<div class="player__scrubber" on:mousedrag="_scrubber_input(event)">
<span class="scrubber__track__played" style="width: {{progress + '%'}}"></span>
<span class="scrubber__track__handle" style="left: {{progress + '%'}}"></span>
</div>
<div class="player__controls no-select">
<ul class="controls__group controls__group--left">
{{#if isFrameMode}}
<li class="controls__icon" on:click="prevFrame()"><i class="fa fa-chevron-left"></i></li>
<li class="controls__icon" on:click="nextFrame()"><i class="fa fa-chevron-right"></i></li>
<span class="controls__frameCounter">{{currentFrame}} / {{meta.frameCount}}</span>
{{else}}
<li class="controls__icon" on:click="togglePlay()"><i class="fa {{isPlaying ? 'fa-pause' : 'fa-play'}}"></i></li>
{{/if}}
</ul>
<ul class="controls__group controls__group--right">
<li class="controls__icon" on:click="toggleFrameMode()"><i class="fa {{isFrameMode ? 'fa-video-camera' : 'fa-film'}}"></i></li>
<li class="controls__icon" on:click="toggleLoop()"><i class="fa {{meta.loop ? 'fa-repeat' : 'fa-arrow-right'}}"></i></li>
</ul>
</div>
</div>
<script>
// Round a number to a given decimal precision
const roundToFixed = function (num, precision) {
return parseFloat(num.toFixed(precision));
};
// Lookup table for Flipnote Studio PPM speeds -> framerate as frames/second
const frameRates = {
1: 0.5,
2: 1,
3: 2,
4: 4,
5: 6,
6: 12,
7: 20,
8: 30
};
export default {
// Component creation hook
oncreate () {
this._video = this.refs.video;
let meta = this.get("meta");
// Calculate how long each frame is held for
meta.frameHold = 1 / frameRates[meta.speed],
this.set({meta: meta});
if (this.get("useArrowKeys")) {
document.addEventListener("keydown", this._arrow_key_handler.bind(this));
}
},
data: function () {
return {
currentFrame: 1,
progress: 0,
isPlaying: false,
isScrubbing: false,
isFrameMode: false
}
},
methods: {
/* BASIC PLAYBACK */
play: function () {
if (!this.get("isFrameMode")) {
this._video.play();
}
},
pause: function () {
this._video.pause();
},
togglePlay: function () {
let isPlaying = this.get("isPlaying");
var fn = isPlaying ? this.pause : this.play;
fn.call(this);
},
toggleLoop: function () {
let meta = this.get("meta");
meta.loop = !meta.loop;
this.set({meta: meta});
},
/* ENTERING / LEAVING FRAME MODE */
enterFrameMode: function () {
this.pause();
this.set({isFrameMode: true});
this.snapFrame();
},
leaveFrameMode: function () {
this.set({isFrameMode: false});
},
toggleFrameMode: function () {
let isFrameMode = this.get("isFrameMode");
var fn = isFrameMode ? this.leaveFrameMode : this.enterFrameMode;
fn.call(this);
},
/* FRAME MODE PLAYBACK */
// Set the current video position to a given frame
setFrame: function (frameIndex) {
if (this.get("isFrameMode")) {
let meta = this.get("meta");
this._video.currentTime = roundToFixed(meta.frameHold * (frameIndex + 1), 3);
this.set({currentFrame: frameIndex});
}
},
// "Snap" the current video position to the nearest frame offset
snapFrame: function () {
let meta = this.get("meta");
let frame = roundToFixed(this._video.currentTime / meta.frameHold, 0) - 1;
// Clamp the value to between 1 and the number of frames
frame = Math.min(Math.max(frame, 1), meta.frameCount);
this.setFrame(frame);
},
// Set the video position from a percentage
setProgress: function (percent) {
let video = this._video;
let meta = this.get("meta");
if (meta.loop) percent = Math.min(percent, 99.9);
let time = (video.duration / 100) * percent;
video.currentTime = time;
this.snapFrame();
},
nextFrame: function () {
let currentFrame = this.get("currentFrame");
let meta = this.get("meta");
let frame = currentFrame + 1;
if (currentFrame == meta.frameCount) frame = meta.loop ? 1 : meta.frameCount;
this.setFrame(frame);
},
prevFrame: function () {
let currentFrame = this.get("currentFrame");
let meta = this.get("meta");
let frame = currentFrame - 1;
if (currentFrame == 1) frame = meta.loop ? meta.frameCount : 1;
this.setFrame(frame);
},
/* UI EVENT HANDLERS */
_video_play: function () {
this.set({isPlaying: true})
},
_video_pause: function () {
this.set({isPlaying: false})
},
_video_timeupdate: function () {
let progress = (this._video.currentTime / this._video.duration) * 100;
this.set({progress: progress});
},
_scrubber_input: function (event) {
let clientRect = this.refs.player.getBoundingClientRect();
let x = Math.max(clientRect.left, Math.min(clientRect.right, event.clientX)) - clientRect.left;
let percent = (x / clientRect.width) * 100;
this.setProgress(percent);
},
_arrow_key_handler: function (event) {
switch (event.keyCode) {
case 37:
this.prevFrame();
break;
case 39:
this.nextFrame();
break;
}
}
},
events: {
mousedrag(node, callback) {
let mouseIsDown = false;
function onmousemove(event) {
if (mouseIsDown) {
// Make sure that dragging the mouse doesn't start selecting text on the page
document.body.classList.add("no-select");
callback(event);
node.classList.add("is-active");
}
};
function onmousedown(event) {
callback(event);
mouseIsDown = true;
document.addEventListener("mousemove", onmousemove, false);
document.addEventListener("mouseup", onmouseup, false);
};
function onmouseup(event) {
mouseIsDown = false;
document.body.classList.remove("no-select");
node.classList.remove("is-active");
document.removeEventListener("mousemove", onmousemove, false);
document.removeEventListener("mouseup", onmouseup, false);
};
node.addEventListener("mousedown", onmousedown, false);
return {
teardown () {
node.removeEventListener("mousedown", onmousedown, false);
}
};
}
}
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment