impl media interactions recording

close #159
close #72
listen to HTMLMediaElement's play/pause events, and replay them
by programmatically play and pause the target element.
This commit is contained in:
Yanzhen Yu
2020-01-12 21:37:01 +08:00
parent 2d07b37701
commit abfb90a778
5 changed files with 89 additions and 4 deletions

View File

@@ -58,7 +58,7 @@
"dependencies": { "dependencies": {
"@types/smoothscroll-polyfill": "^0.3.0", "@types/smoothscroll-polyfill": "^0.3.0",
"mitt": "^1.1.3", "mitt": "^1.1.3",
"rrweb-snapshot": "^0.7.23", "rrweb-snapshot": "file:../snapshot",
"smoothscroll-polyfill": "^0.4.3" "smoothscroll-polyfill": "^0.4.3"
} }
} }

View File

@@ -35,7 +35,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
inlineStylesheet = true, inlineStylesheet = true,
maskAllInputs = false, maskAllInputs = false,
hooks, hooks,
mousemoveWait = 50 mousemoveWait = 50,
} = options; } = options;
// runtime checks for user options // runtime checks for user options
if (!emit) { if (!emit) {
@@ -178,11 +178,21 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}, },
}), }),
), ),
mediaInteractionCb: p =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MediaInteraction,
...p,
},
}),
),
blockClass, blockClass,
ignoreClass, ignoreClass,
maskAllInputs, maskAllInputs,
inlineStylesheet, inlineStylesheet,
mousemoveWait mousemoveWait,
}, },
hooks, hooks,
), ),

View File

@@ -31,6 +31,8 @@ import {
IncrementalSource, IncrementalSource,
hooksParam, hooksParam,
Arguments, Arguments,
mediaInteractionCallback,
MediaInteractions,
} from '../types'; } from '../types';
import { deepDelete, isParentRemoved, isAncestorInSet } from './collection'; import { deepDelete, isParentRemoved, isAncestorInSet } from './collection';
@@ -517,6 +519,26 @@ function initInputObserver(
}; };
} }
function initMediaInteractionObserver(
mediaInteractionCb: mediaInteractionCallback,
blockClass: blockClass,
): listenerHandler {
const handler = (type: 'play' | 'pause') => (event: Event) => {
const { target } = event;
if (!target || isBlocked(target as Node, blockClass)) {
return;
}
mediaInteractionCb({
type: type === 'play' ? MediaInteractions.Play : MediaInteractions.Pause,
id: mirror.getId(target as INode),
});
};
const handlers = [on('play', handler('play')), on('pause', handler('pause'))];
return () => {
handlers.forEach(h => h());
};
}
function mergeHooks(o: observerParam, hooks: hooksParam) { function mergeHooks(o: observerParam, hooks: hooksParam) {
const { const {
mutationCb, mutationCb,
@@ -525,6 +547,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
scrollCb, scrollCb,
viewportResizeCb, viewportResizeCb,
inputCb, inputCb,
mediaInteractionCb,
} = o; } = o;
o.mutationCb = (...p: Arguments<mutationCallBack>) => { o.mutationCb = (...p: Arguments<mutationCallBack>) => {
if (hooks.mutation) { if (hooks.mutation) {
@@ -562,6 +585,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
} }
inputCb(...p); inputCb(...p);
}; };
o.mediaInteractionCb = (...p: Arguments<mediaInteractionCallback>) => {
if (hooks.mediaInteaction) {
hooks.mediaInteaction(...p);
}
mediaInteractionCb(...p);
};
} }
export default function initObservers( export default function initObservers(
@@ -588,6 +617,10 @@ export default function initObservers(
o.ignoreClass, o.ignoreClass,
o.maskAllInputs, o.maskAllInputs,
); );
const mediaInteractionHandler = initMediaInteractionObserver(
o.mediaInteractionCb,
o.blockClass,
);
return () => { return () => {
mutationObserver.disconnect(); mutationObserver.disconnect();
mousemoveHandler(); mousemoveHandler();
@@ -595,5 +628,6 @@ export default function initObservers(
scrollHandler(); scrollHandler();
viewportResizeHandler(); viewportResizeHandler();
inputHandler(); inputHandler();
mediaInteractionHandler();
}; };
} }

View File

@@ -20,6 +20,7 @@ import {
ReplayerEvents, ReplayerEvents,
Handler, Handler,
Emitter, Emitter,
MediaInteractions,
} from '../types'; } from '../types';
import { mirror, polyfill } from '../utils'; import { mirror, polyfill } from '../utils';
import getInjectStyleRules from './styles/inject-style'; import getInjectStyleRules from './styles/inject-style';
@@ -587,6 +588,26 @@ export class Replayer {
} }
break; break;
} }
case IncrementalSource.MediaInteraction: {
const target = mirror.getNode(d.id);
if (!target) {
return this.debugNodeNotFound(d, d.id);
}
const mediaEl = (target as Node) as HTMLMediaElement;
if (d.type === MediaInteractions.Pause) {
mediaEl.pause();
}
if (d.type === MediaInteractions.Play) {
if (mediaEl.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
mediaEl.play();
} else {
mediaEl.addEventListener('canplay', () => {
mediaEl.play();
});
}
}
break;
}
default: default:
} }
} }

View File

@@ -60,6 +60,7 @@ export enum IncrementalSource {
ViewportResize, ViewportResize,
Input, Input,
TouchMove, TouchMove,
MediaInteraction,
} }
export type mutationData = { export type mutationData = {
@@ -88,13 +89,18 @@ export type inputData = {
id: number; id: number;
} & inputValue; } & inputValue;
export type mediaInteractionData = {
source: IncrementalSource.MediaInteraction;
} & mediaInteractionParam;
export type incrementalData = export type incrementalData =
| mutationData | mutationData
| mousemoveData | mousemoveData
| mouseInteractionData | mouseInteractionData
| scrollData | scrollData
| viewportResizeData | viewportResizeData
| inputData; | inputData
| mediaInteractionData;
export type event = export type event =
| domContentLoadedEvent | domContentLoadedEvent
@@ -130,6 +136,7 @@ export type observerParam = {
scrollCb: scrollCallback; scrollCb: scrollCallback;
viewportResizeCb: viewportResizeCallback; viewportResizeCb: viewportResizeCallback;
inputCb: inputCallback; inputCb: inputCallback;
mediaInteractionCb: mediaInteractionCallback;
blockClass: blockClass; blockClass: blockClass;
ignoreClass: string; ignoreClass: string;
maskAllInputs: boolean; maskAllInputs: boolean;
@@ -144,6 +151,7 @@ export type hooksParam = {
scroll?: scrollCallback; scroll?: scrollCallback;
viewportResize?: viewportResizeCallback; viewportResize?: viewportResizeCallback;
input?: inputCallback; input?: inputCallback;
mediaInteaction?: mediaInteractionCallback;
}; };
export type textCursor = { export type textCursor = {
@@ -245,6 +253,18 @@ export type inputValue = {
export type inputCallback = (v: inputValue & { id: number }) => void; export type inputCallback = (v: inputValue & { id: number }) => void;
export const enum MediaInteractions {
Play,
Pause,
}
export type mediaInteractionParam = {
type: MediaInteractions;
id: number;
};
export type mediaInteractionCallback = (p: mediaInteractionParam) => void;
export type Mirror = { export type Mirror = {
map: idNodeMap; map: idNodeMap;
getId: (n: INode) => number; getId: (n: INode) => number;