add player controller
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
<h1>
|
||||
rrweb player playground
|
||||
</h1>
|
||||
<Player {events} />
|
||||
|
||||
<style>
|
||||
|
||||
108
src/Controller.html
Normal file
108
src/Controller.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<div class="rr-controller">
|
||||
<div class="rr-timeline">
|
||||
<span class="rr-timeline__time">{formatTime(currentTime)}</span>
|
||||
<div class="rr-progress">
|
||||
<div class="rr-progress__step" ref:step style="width: {percentage}"></div>
|
||||
<div class="rr-progress__handler" ref:handler style="left: {percentage}"></div>
|
||||
</div>
|
||||
<span class="rr-timeline__time">{formatTime(meta.totalTime)}</span>
|
||||
</div>
|
||||
<button>pause</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { formatTime } from './utils.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentTime: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
meta({ replayer }) {
|
||||
return replayer.getMetaData();
|
||||
},
|
||||
percentage({ currentTime, meta }) {
|
||||
return `${100 * currentTime / meta.totalTime}%`;
|
||||
},
|
||||
},
|
||||
helpers: {
|
||||
formatTime,
|
||||
},
|
||||
methods: {
|
||||
loopTimer() {
|
||||
const now = performance.now();
|
||||
const self = this;
|
||||
|
||||
function update(step) {
|
||||
let { currentTime, meta } = self.get();
|
||||
currentTime = Math.floor(step - now);
|
||||
self.set({ currentTime });
|
||||
|
||||
if (currentTime < meta.totalTime) {
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(update);
|
||||
},
|
||||
},
|
||||
onupdate({ changed, current }) {
|
||||
if (changed.replayer) {
|
||||
this.loopTimer();
|
||||
current.replayer.play();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rr-controller {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.rr-timeline {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.rr-timeline__time {
|
||||
padding: 0 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.rr-progress {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: white;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.rr-progress__step {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: orange
|
||||
}
|
||||
|
||||
.rr-progress__handler {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
transform: translate(-50%, -50%);
|
||||
background: orange;
|
||||
}
|
||||
</style>
|
||||
114
src/Player.html
114
src/Player.html
@@ -1,10 +1,65 @@
|
||||
<div class="rr-player">
|
||||
<div class="rr-player__frame" ref:frame { style }></div>
|
||||
{#if replayer}
|
||||
<Controller { replayer } />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { Replayer } from 'rrweb';
|
||||
import { inlineCss } from './utils.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Controller: './Controller.html',
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
width: 1024,
|
||||
height: 576,
|
||||
events: [],
|
||||
replayer: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
style({ width, height }) {
|
||||
return inlineCss({
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateScale(el, frameDimension) {
|
||||
const { width, height } = this.get();
|
||||
const widthScale = (width - 20) / frameDimension.width;
|
||||
const heightScale = (height - 20) / frameDimension.height;
|
||||
el.style.transform =
|
||||
`scale(${Math.min(widthScale, heightScale, 1)})` +
|
||||
'translate(-50%, -50%)';
|
||||
},
|
||||
},
|
||||
oncreate(p) {
|
||||
const { events } = this.get();
|
||||
const replayer = new Replayer(events, {
|
||||
speed: 1,
|
||||
root: this.refs.frame,
|
||||
});
|
||||
replayer.on('resize', (dimension) =>
|
||||
this.updateScale(replayer.wrapper, dimension)
|
||||
);
|
||||
this.set({
|
||||
replayer,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rr-player {
|
||||
border: 3px solid indianred;
|
||||
background: white;
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
@@ -17,9 +72,14 @@
|
||||
float: left;
|
||||
clear: both;
|
||||
transform-origin: top left;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border: 2px solid grey;
|
||||
left: calc(50% - 10px);
|
||||
top: calc(50% - 10px);
|
||||
margin: 10px;
|
||||
box-shadow: 0 3px 28px rgba(0, 0, 0, 0.25), 0 1px 10px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
:global(.replayer-wrapper > iframe) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* get from rrweb */
|
||||
@@ -43,48 +103,4 @@
|
||||
transform: translate(-10px, -10px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { Replayer } from 'rrweb';
|
||||
import { inlineCss } from './util.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
width: 1024,
|
||||
height: 576,
|
||||
events: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
style({ width, height }) {
|
||||
return inlineCss({
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateScale(el, frameDimension) {
|
||||
const { width, height } = this.get();
|
||||
const widthScale = width / frameDimension.width;
|
||||
const heightScale = height / frameDimension.height;
|
||||
el.style.transform =
|
||||
`scale(${Math.min(widthScale, heightScale, 1)})` +
|
||||
'translate(-50%, -50%)';
|
||||
},
|
||||
},
|
||||
oncreate(p) {
|
||||
const { events } = this.get();
|
||||
const replayer = new Replayer(events, {
|
||||
speed: 1,
|
||||
root: this.refs.frame,
|
||||
onResize: (dimension) => this.updateScale(replayer.wrapper, dimension),
|
||||
});
|
||||
replayer.play();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
</style>
|
||||
@@ -1,7 +0,0 @@
|
||||
export function inlineCss(cssObj) {
|
||||
let style = '';
|
||||
Object.keys(cssObj).forEach(key => {
|
||||
style += `${key}: ${cssObj[key]};`;
|
||||
});
|
||||
return style;
|
||||
}
|
||||
33
src/utils.js
Normal file
33
src/utils.js
Normal file
@@ -0,0 +1,33 @@
|
||||
export function inlineCss(cssObj) {
|
||||
let style = '';
|
||||
Object.keys(cssObj).forEach(key => {
|
||||
style += `${key}: ${cssObj[key]};`;
|
||||
});
|
||||
return style;
|
||||
}
|
||||
|
||||
function padZero(num, len = 2) {
|
||||
const threshold = Math.pow(10, len - 1);
|
||||
if (num < threshold) {
|
||||
num = String(num);
|
||||
while (String(threshold).length > num.length) {
|
||||
num = '0' + num;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
const SECOND = 1000;
|
||||
const MINUTE = 60 * SECOND;
|
||||
const HOUR = 60 * MINUTE;
|
||||
export function formatTime(ms) {
|
||||
const hour = Math.floor(ms / HOUR);
|
||||
ms = ms % HOUR;
|
||||
const minute = Math.floor(ms / MINUTE);
|
||||
ms = ms % MINUTE;
|
||||
const second = Math.floor(ms / SECOND);
|
||||
if (hour) {
|
||||
return `${padZero(hour)}:${padZero(minute)}:${padZero(minute)}`;
|
||||
}
|
||||
return `${padZero(hour)}:${padZero(second)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user