Skip to content

Commit

Permalink
Merge branch 'EmulatorJS:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
CycloKid authored Nov 10, 2023
2 parents 0b4eb88 + 1b3a17f commit 7d97ab7
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 110 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Self-hosted **Javascript** emulation for various system.

Join our Discord server:

[![Join our Discord server!](https://invite.casperiv.dev/?inviteCode=6akryGkETU&format=svg)](https://discord.gg/6akryGkETU)
[![Join our Discord server!](https://invidget.switchblade.xyz/6akryGkETU)](https://discord.gg/6akryGkETU)

</div>

Expand Down
128 changes: 128 additions & 0 deletions data/emulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,27 @@ class EmulatorJS {
a.click();
hideMenu();
});

let screenMediaRecorder = null;
const startScreenRecording = addButton("Start screen recording", false, () => {
if (screenMediaRecorder !== null) {
screenMediaRecorder.stop();
}
screenMediaRecorder = this.screenRecord();
startScreenRecording.setAttribute("hidden", "hidden");
stopScreenRecording.removeAttribute("hidden");
hideMenu();
});
const stopScreenRecording = addButton("Stop screen recording", true, () => {
if (screenMediaRecorder !== null) {
screenMediaRecorder.stop();
screenMediaRecorder = null;
}
startScreenRecording.removeAttribute("hidden");
stopScreenRecording.setAttribute("hidden", "hidden");
hideMenu();
});

const qSave = addButton("Quick Save", false, () => {
const slot = this.settings['save-state-slot'] ? this.settings['save-state-slot'] : "1";
this.gameManager.quickSave(slot);
Expand All @@ -1295,6 +1316,8 @@ class EmulatorJS {
});
this.elements.contextMenu = {
screenshot: screenshot,
startScreenRecording: startScreenRecording,
stopScreenRecording: stopScreenRecording,
save: qSave,
load: qLoad
}
Expand Down Expand Up @@ -1395,6 +1418,7 @@ class EmulatorJS {

if (this.config.buttonOpts) {
if (this.config.buttonOpts.screenshot === false) screenshot.setAttribute("hidden", "");
if (this.config.buttonOpts.screenRecord === false) startScreenRecording.setAttribute("hidden", "");
if (this.config.buttonOpts.quickSave === false) qSave.setAttribute("hidden", "");
if (this.config.buttonOpts.quickLoad === false) qLoad.setAttribute("hidden", "");
}
Expand Down Expand Up @@ -4786,5 +4810,109 @@ class EmulatorJS {
cheatChanged(checked, code, index) {
this.gameManager.setCheat(index, checked, code);
}

collectScreenRecordingMediaTracks(canvasEl, fps) {
let videoTrack = null;
const videoTracks = canvasEl.captureStream(fps).getVideoTracks();
if (videoTracks.length !== 0) {
videoTrack = videoTracks[0];
} else {
console.error('Unable to capture video stream');
return null;
}

let audioTrack = null;
if (window.AL && window.AL.currentCtx && window.AL.currentCtx.audioCtx) {
const alContext = window.AL.currentCtx;
const audioContext = alContext.audioCtx;

const gainNodes = [];
for (let sourceIdx in alContext.sources) {
gainNodes.push(alContext.sources[sourceIdx].gain);
}

const merger = audioContext.createChannelMerger(gainNodes.length);
gainNodes.forEach(node => node.connect(merger));

const destination = audioContext.createMediaStreamDestination();
merger.connect(destination);

const audioTracks = destination.stream.getAudioTracks();
if (audioTracks.length !== 0) {
audioTrack = audioTracks[0];
}
}

const stream = new MediaStream();
if (videoTrack && videoTrack.readyState === 'live') {
stream.addTrack(videoTrack);
}
if (audioTrack && audioTrack.readyState === 'live') {
stream.addTrack(audioTrack);
}
return stream;
}

screenRecord() {
const captureScreenWidth= (this.config.screenRecording && (typeof this.config.screenRecording.width == "number")) ? this.config.screenRecording.width : 800;
const captureScreenHeight = (this.config.screenRecording && (typeof this.config.screenRecording.height == "number")) ? this.config.screenRecording.height : 600;
const captureFps = (this.config.screenRecording && (typeof this.config.screenRecording.fps == "number")) ? this.config.screenRecording.fps : 30;
const captureVideoBitrate = (this.config.screenRecording && (typeof this.config.screenRecording.videoBitrate == "number")) ? this.config.screenRecording.videoBitrate : 2 * 1024 * 1014;
const captureAudioBitrate = (this.config.screenRecording && (typeof this.config.screenRecording.audioBitrate == "number")) ? this.config.screenRecording.audioBitrate : 256 * 1024;

const captureCanvas = document.createElement('canvas');
captureCanvas.width = captureScreenWidth;
captureCanvas.height = captureScreenHeight;
captureCanvas.style.position = 'absolute';
captureCanvas.style.top = '-999px';
captureCanvas.style.bottom = '-999px';
document.getElementsByTagName('body')[0].append(captureCanvas);

const captureCtx = captureCanvas.getContext('2d', { alpha: false });
captureCtx.fillStyle = '#000';

let animation = true;

const drawNextFrame = () => {
const scaleX = captureScreenWidth / this.canvas.width;
const scaleY = captureScreenHeight / this.canvas.height;
const scale = Math.max(scaleY, scaleX);
const width = this.canvas.width * scale;
const height = this.canvas.height * scale;
const startX = (captureScreenWidth - width) / 2;
const startY = (captureScreenHeight - height) / 2;
captureCtx.drawImage(this.canvas, Math.round(startX), Math.round(startY), Math.round(width), Math.round(height));
if (animation) {
requestAnimationFrame(drawNextFrame);
}
};
requestAnimationFrame(drawNextFrame);

const chunks = [];
const tracks = this.collectScreenRecordingMediaTracks(captureCanvas, captureFps);
const recorder = new MediaRecorder(tracks, {
videoBitsPerSecond: captureVideoBitrate,
audioBitsPerSecond: captureAudioBitrate,
});
recorder.addEventListener('dataavailable', e => {
chunks.push(e.data);
});
recorder.addEventListener('stop', () => {
const blob = new Blob(chunks);
const url = URL.createObjectURL(blob);
const date = new Date();
const a = document.createElement('a');
a.href = url;
a.download = this.getBaseFileName()+"-"+date.getMonth()+"-"+date.getDate()+"-"+date.getFullYear()+".webm";
a.click();

animation = false;
captureCanvas.remove();
});
recorder.start();

return recorder;
}

}
window.EmulatorJS = EmulatorJS;
2 changes: 1 addition & 1 deletion data/emulator.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions data/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
config.disableCue = window.EJS_disableCue;
config.startBtnName = window.EJS_startButtonName;
config.softLoad = window.EJS_softLoad;
config.screenRecording = window.EJS_screenRecording;

if (typeof window.EJS_language === "string" && window.EJS_language !== "en-US") {
try {
Expand Down
Loading

0 comments on commit 7d97ab7

Please sign in to comment.