Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Closes #2
  • Loading branch information
WofWca committed May 21, 2023
1 parent 8f197e5 commit 6fa66ce
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import SilenceDetectorNode, { SilenceDetectorEventType, SilenceDetectorMessage }
from '@/entry-points/content/SilenceDetector/SilenceDetectorNode';
import VolumeFilterNode from '@/entry-points/content/VolumeFilter/VolumeFilterNode';
import lookaheadVolumeFilterSmoothing from './lookaheadVolumeFilterSmoothing.json'
import * as mediaSourcesCloning from '../cloneMediaSources';


// A more semantically correct version would be `Array<[start: MediaTime, end: MediaTime]>`,
// but I think this is a bit faster.
Expand Down Expand Up @@ -87,7 +89,18 @@ export default class Lookahead {
// TODO also need to watch for changes of `crossOrigin`
// (in `ElementPlaybackControllerCloning.ts`).
clone.crossOrigin = originalElement.crossOrigin;
clone.src = originalElement.currentSrc;
// TODO what if `originalElement.srcObject`
const mediaSourceFromSrcUrl =
mediaSourcesCloning.getMediaSourceFromObjectUrl(originalElement.src);
if (mediaSourceFromSrcUrl) {
// Reusing the same `mediaSourceFromSrcUrl` for the clone element is not possible, see
// https://github.com/WofWca/jumpcutter/issues/2
const cloneMediaSource = mediaSourcesCloning.getMediaSourceClone(mediaSourceFromSrcUrl);
// Yes, we could also do `clone.src = URL.createObjectURL(cloneMediaSource)`
clone.srcObject = cloneMediaSource;
} else {
clone.src = originalElement.currentSrc;
}

// Not doing this appears to cause a resource (memory and processing) leak in Chromium manifesting itself when
// creating new instances of Lookahead (and discarding the old ones).
Expand Down
249 changes: 249 additions & 0 deletions src/entry-points/content/cloneMediaSources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/**
* @license
* Copyright (C) 2023 WofWca <[email protected]>
* Copyright (C) 2023 Jonas Herzig <[email protected]>
*
* This file is part of Jump Cutter Browser Extension.
*
* Jump Cutter Browser Extension is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jump Cutter Browser Extension is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Jump Cutter Browser Extension. If not, see <https://www.gnu.org/licenses/>.
*/



// function startIntercepting
// function createInterceptor(
// functionName: string,
// object: object,
// callback: (args: unknown[])
// )
/**
* @returns stop intercepting
*/
function startInterceptingCreateObjectUrlCalls(
callback:
(
args: Parameters<typeof URL.createObjectURL>,
retVal: ReturnType<typeof URL.createObjectURL>,
) => void,
): () => void {
type MutatedURL = typeof URL & {
_jumpCutterExtensionOriginalCreateObjectURL: typeof URL.createObjectURL
};
(URL as MutatedURL)._jumpCutterExtensionOriginalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(...args) {
const originalRetVal =
(URL as MutatedURL)._jumpCutterExtensionOriginalCreateObjectURL(...args);
callback(args, originalRetVal);

console.log('createObjectURL', args, originalRetVal);

return originalRetVal;
}

return () => {
URL.createObjectURL = (URL as MutatedURL)._jumpCutterExtensionOriginalCreateObjectURL;
//@ts-expect-error 2790
delete (URL as MutatedURL)._jumpCutterExtensionOriginalCreateObjectURL;
}
}

function startInterceptingMediaSourceConstructorCalls(
callback:
(
constructorArgs: ConstructorParameters<typeof MediaSource>,
newMediaSource: MediaSource,
) => void,
): () => void {
globalThis._jumpCutterExtensionOriginalMediaSource = globalThis.MediaSource;
// TODO big yikes, you forgot the static methods (`isTypeSupported`).
// globalThis.MediaSource = function(...args) {
const proxy = function(...args) {
const originalRetVal =
new globalThis._jumpCutterExtensionOriginalMediaSource(...args);

console.log('gottem', args, originalRetVal);

callback(args, originalRetVal);
// Yes, `new currFunc()` will evalate to return value, and not a freshly created empty object.
// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new#description
// TODO fix: fuck me, this means that `(new MediaSource()) instanceof MediaSource` will
// always be false...
return originalRetVal;
}
proxy.isTypeSupported = globalThis.MediaSource.isTypeSupported;
globalThis.MediaSource = proxy;

return () => {
globalThis.MediaSource = globalThis._jumpCutterExtensionOriginalMediaSource;
delete globalThis._jumpCutterExtensionOriginalMediaSource
}
}


type ObjectUrlToItsMediaSource = Record<
ReturnType<typeof URL.createObjectURL>,
MediaSource
>;
/**
* Creates and maintains a map of all URLs created with `URL.createObjectURL`
* to the `MediaSource` that the URL was created for.
* When `URL.revokeObjectURL` is called for a URL, it's also automatically
* removed from the map.
*
* Keep in mind that `el.src` can also be made out of `MediaSourceHandle`.
*/
function createMaintainedObjectUrlToMediaSourceMap(): [
map: ObjectUrlToItsMediaSource,
stopWatching: () => void,
] {
const map: ObjectUrlToItsMediaSource = {};

// // Override the original functions. This can probably make things go wrong,
// // but I don't know a better way.
// type MutatedURL = typeof URL & {
// _jumpCutterExtensionOriginalCreateObjectURL: typeof URL.createObjectURL
// };
// (URL as MutatedURL)._jumpCutterExtensionOriginalCreateObjectURL = URL.createObjectURL;
// URL.createObjectURL = function(obj, ...rest) {
// const originalRetVal =
// (URL as MutatedURL)._jumpCutterExtensionOriginalCreateObjectURL(obj, ...rest);

// if (obj instanceof MediaSource) {
// const url = originalRetVal;
// map[url] = obj;
// }
// }

const stopInterceptingCreateObjectUrlCalls = startInterceptingCreateObjectUrlCalls(([obj], url) => {
// TODO see comment about `instanceof MediaSource` above.
// if (obj instanceof MediaSource) {
if (true) {
map[url] = obj;
}
});
// TODO perf: also watch `revokeObjectURL` and remove them from the map so there is no memory
// leak.

return [
map,
() => {
stopInterceptingCreateObjectUrlCalls();
}
];
}

function makeMaintainedMediaSourceClone(
mediaSourceConstructorArgs,
_originalMediaSource,
// onCloneReady: (cloneMediaSource: MediaSource) => void,
): MediaSource {
const cloneMediaSource =
new _jumpCutterExtensionOriginalMediaSource(...mediaSourceConstructorArgs) as MediaSource;

// TODO refactor: DRY interception code.
type MutatedMediaSource = MediaSource & {
originalAddSourceBuffer: MediaSource['addSourceBuffer']
};
// TODO perf: a way to revert all the objects to the original state and stop
// maintaining the clone?
const originalMediaSource = _originalMediaSource as MutatedMediaSource;
originalMediaSource.originalAddSourceBuffer = originalMediaSource.addSourceBuffer;
originalMediaSource.addSourceBuffer = function(type, ...rest) {
const originalRetVal = originalMediaSource.originalAddSourceBuffer(type, ...rest);
// TODO I believe we need to check for `readyState`.


// cloneMediaSource.addEventListener('sourceopen')


const cloneSourceBuffer = cloneMediaSource.addSourceBuffer(type, ...rest);

console.log('addSourceBuffer', [type, ...rest] , originalRetVal);

const _originalSourceBuffer = originalRetVal;
type MutatedSourceBuffer = SourceBuffer & {
originalAppendBuffer: SourceBuffer['appendBuffer']
};
const originalSourceBuffer = _originalSourceBuffer as MutatedSourceBuffer;

// TODO refactor: DRY these interceptors already, dude.
// And un-nest them.
originalSourceBuffer.originalAppendBuffer = originalSourceBuffer.appendBuffer;
originalSourceBuffer.appendBuffer = function(data, ...rest) {
const originalRetVal = originalSourceBuffer.originalAppendBuffer(data, ...rest);
// debugger;
console.log('appendBuffer', [data, ...rest] , originalRetVal);
cloneSourceBuffer.appendBuffer(data, ...rest);
return originalRetVal;
}

return originalRetVal;
}

return cloneMediaSource
// TODO intercept `removeSourceBuffer` as well. And other functions maybe.
}

function createMaintainedMediaSourceToMediaSourceCloneMap(): [
map: WeakMap<MediaSource, MediaSource>,
stopMaintaining: () => void,
] {
const map = new WeakMap<MediaSource, MediaSource>();
const stopInterceptingMediaSourceConstructorCalls = startInterceptingMediaSourceConstructorCalls((constructorArgs, originalMediaSource) => {
// TODO handle `stopMaintainingMediaSourceClone`.
const maintainedClone = makeMaintainedMediaSourceClone(constructorArgs, originalMediaSource);
map.set(originalMediaSource, maintainedClone);
})
return [
map,
() => stopInterceptingMediaSourceConstructorCalls(),
];
}

// export function watchMediaSources(): [
// export function startCloningMediaSources(): [
// objectUrlToMediaSource: ObjectUrlToItsMediaSource,
// ] {
// }

/* export */ function startCloningMediaSources(): [
// objectUrlToMediaSource:
getMediaSourceFromObjectUrl:
(url: ReturnType<typeof URL.createObjectURL>) => MediaSource | undefined,
getMediaSourceClone: (originalMediaSource: MediaSource) => MediaSource,
stopCloningMediaSources: () => void,
] {
const [objectUrlToMediaSourceMap, stopMaintainingUrlMap]
= createMaintainedObjectUrlToMediaSourceMap();
const [mediaSourceToMediaSourceCloneMap, stopMaintainingCloneMap]
= createMaintainedMediaSourceToMediaSourceCloneMap();

return [
(url) => objectUrlToMediaSourceMap[url],
(originalMediaSource) => mediaSourceToMediaSourceCloneMap.get(originalMediaSource)!,
() => {
stopMaintainingUrlMap();
stopMaintainingCloneMap();
},
]
}

// TODO export just `startCloningMediaSources`, don't call it at the top level.
// export const [
// getMediaSourceFromObjectUrl,
// getMediaSourceClone,
// stopCloningMediaSources,
// ] = startCloningMediaSources();

window.testCloneMediaSources = startCloningMediaSources();
36 changes: 35 additions & 1 deletion src/entry-points/content/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @license
* Copyright (C) 2020, 2021, 2022 WofWca <[email protected]>
* Copyright (C) 2020, 2021, 2022, 2023 WofWca <[email protected]>
* Copyright (C) 2023 Jonas Herzig <[email protected]>
*
* This file is part of Jump Cutter Browser Extension.
*
Expand All @@ -21,6 +22,39 @@
import { enabledSettingDefaultValue, MyStorageChanges, Settings } from '@/settings';
import { mainStorageAreaName } from '@/settings/mainStorageAreaName';
import { browserOrChrome } from '@/webextensions-api-browser-or-chrome';
// Import so we start tracking right after initialization.
// TODO refactor: don't import for side effects

// import * as asdasd from './cloneMediaSources';
// console.log(asdasd);



// window.testtt = 1;
// globalThis.testttt = 2;
// console.log('testttt');


// console.log(import('./test'));



// const actualCode = `window.testScriptTag = 'isNice'`;

// const script = document.createElement('script');
// script.textContent = actualCode;
// (document.head||document.documentElement).appendChild(script);
// script.remove();




const s = document.createElement('script');
s.src = browser.runtime.getURL('content/cloneMediaSources.js');
s.onload = function() { this.remove(); };
(document.head || document.documentElement).appendChild(s);



(async function () { // Just for top-level `await`

Expand Down
2 changes: 2 additions & 0 deletions src/entry-points/content/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
window.testttImport = 1;
console.log('imported');
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"matches": ["http://*/*", "https://*/*"],
"js": ["content/main.js"],
"all_frames": true,
"run_at": "document_idle",
"run_at": "document_start",
"match_about_blank": true
}
],
Expand Down
9 changes: 8 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ module.exports = env => {

entry: {
content: './src/entry-points/content/main.ts',
cloneMediaSources: './src/entry-points/content/cloneMediaSources.ts',
SilenceDetectorProcessor: './src/entry-points/content/SilenceDetector/SilenceDetectorProcessor.ts',
VolumeFilterProcessor: './src/entry-points/content/VolumeFilter/VolumeFilterProcessor.ts',

Expand All @@ -107,7 +108,13 @@ module.exports = env => {
path: path.resolve(__dirname, `dist-${env.browser}`),
filename: (pathData, assetInfo) => {
const chunkName = pathData.chunk.name;
if (['SilenceDetectorProcessor', 'VolumeFilterProcessor'].includes(chunkName)) {
if (
[
'cloneMediaSources',
'SilenceDetectorProcessor',
'VolumeFilterProcessor'
].includes(chunkName)
) {
return `content/${chunkName}.js`;
}
return `${chunkName}/main.js`;
Expand Down

0 comments on commit 6fa66ce

Please sign in to comment.