Created
May 5, 2017 17:31
-
-
Save jaames/cc00a3495f8f1efb95b47d2f6663b624 to your computer and use it in GitHub Desktop.
sudomemo video player component, for Svelte (https://svelte.technology/guide)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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