Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/googDevice/toolbox/GoogToolBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export class GoogToolBox extends ToolBox {
elements.push(screenshot);
}

const fullscreen = new ToolBoxButton('Fullscreen Mode', SvgImage.Icon.FULL_SCREEN);
fullscreen.addEventListener('click', () => {
player.openFullscreen(client);
});
elements.push(fullscreen);

const keyboard = new ToolBoxCheckbox(
'Capture keyboard',
SvgImage.Icon.KEYBOARD,
Expand Down
4 changes: 4 additions & 0 deletions src/app/player/BaseCanvasBasedPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ export abstract class BaseCanvasBasedPlayer extends BasePlayer {
if (parent) {
const tag = BaseCanvasBasedPlayer.createElement(this.tag.id);
tag.className = this.tag.className;
if (this.isFullScreen) {
tag.style.maxWidth = '100vw';
tag.style.maxHeight = '100vh';
}
parent.replaceChild(tag, this.tag);
parent.appendChild(this.touchableCanvas);
this.tag = tag;
Expand Down
119 changes: 119 additions & 0 deletions src/app/player/BasePlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Size from '../Size';
import Util from '../Util';
import { TypedEmitter } from '../../common/TypedEmitter';
import { DisplayInfo } from '../DisplayInfo';
import { StreamClientScrcpy } from '../googDevice/client/StreamClientScrcpy';

interface BitrateStat {
timestamp: number;
Expand Down Expand Up @@ -83,6 +84,7 @@ export abstract class BasePlayer extends TypedEmitter<PlayerEvents> {
private statLines: string[] = [];
public readonly supportsScreenshot: boolean = false;
public readonly resizeVideoToBounds: boolean = false;
protected isFullScreen: boolean = false;
protected videoHeight = -1;
protected videoWidth = -1;

Expand Down Expand Up @@ -343,6 +345,123 @@ export abstract class BasePlayer extends TypedEmitter<PlayerEvents> {
return this.touchableCanvas;
}

private static createVideoSettingsWithBounds(old: VideoSettings, newBounds: Size): VideoSettings {
return new VideoSettings({
crop: old.crop,
bitrate: old.bitrate,
bounds: newBounds,
maxFps: old.maxFps,
iFrameInterval: old.iFrameInterval,
sendFrameMeta: old.sendFrameMeta,
lockedVideoOrientation: old.lockedVideoOrientation,
displayId: old.displayId,
codecOptions: old.codecOptions,
encoderName: old.encoderName,
});
}

public openFullscreen(client: StreamClientScrcpy) {
if (!this.parentElement) {
console.warn('Cannot enter fullscreen: no parent element');
return;
}

const element = this.parentElement as any;

// Set up fullscreen styles that maintain an aspect ratio
const scaleScreen = () => {
if (!this.parentElement) return;

// Scale the canvas to fit screen while maintaining aspect ratio
if (this.screenInfo) {
const { width: deviceWidth, height: deviceHeight } = this.screenInfo.videoSize;
console.log(`Device video size: ${deviceWidth}x${deviceHeight}`);
const ratio = window.devicePixelRatio || 1;
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
console.log(`Screen size: ${screenWidth}x${screenHeight}`);
let newBounds;
if (this.isFullScreen) {
newBounds = new Size(screenWidth * ratio, screenHeight * ratio);
this.parentElement.style.display = 'flex';
this.parentElement.style.justifyContent = 'center';
this.parentElement.style.alignItems = 'center';
this.tag.style.maxWidth = '100vw';
this.tag.style.maxHeight = '100vh';
this.touchableCanvas.style.maxWidth = '100vw';
this.touchableCanvas.style.maxHeight = '100vh';
} else {
newBounds = client.getMaxSize();
this.parentElement.style.display = '';
this.parentElement.style.justifyContent = '';
this.parentElement.style.alignItems = '';
this.tag.style.maxWidth = '';
this.tag.style.maxHeight = '';
this.touchableCanvas.style.maxWidth = '';
this.touchableCanvas.style.maxHeight = '';
}
console.log('New bounds:', newBounds);
if (newBounds) {
client.sendNewVideoSetting(BasePlayer.createVideoSettingsWithBounds(this.videoSettings, newBounds));
}
}
};

// Listen for fullscreen change events
const handleFullscreenChange = () => {
const isFullscreen = !!(
document.fullscreenElement ||
(document as any).webkitFullscreenElement ||
(document as any).mozFullScreenElement ||
(document as any).msFullscreenElement
);

if (isFullscreen) {
this.isFullScreen = true;
scaleScreen();
} else {
this.isFullScreen = false;
scaleScreen();
// Remove event listeners
document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
document.removeEventListener('mozfullscreenchange', handleFullscreenChange);
document.removeEventListener('MSFullscreenChange', handleFullscreenChange);
}
};

// Add event listeners for fullscreen changes
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);

try {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
// Chrome, Safari, Opera (older)
element.webkitRequestFullscreen();
} else if (element.webkitRequestFullScreen) {
// Safari (very old)
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
// Firefox older
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
// IE/Edge
element.msRequestFullscreen();
} else if (element.oRequestFullscreen) {
// Opera (very old)
element.oRequestFullscreen();
} else {
console.warn('Fullscreen API is not supported by this browser');
}
} catch (error) {
console.error('Failed to enter fullscreen:', error);
}
}

public setParent(parent: HTMLElement): void {
this.parentElement = parent;
parent.appendChild(this.tag);
Expand Down
5 changes: 5 additions & 0 deletions src/app/ui/SvgImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import MenuSVG from '../../public/images/buttons/menu.svg';
import ArrowBackSVG from '../../public/images/buttons/arrow_back.svg';
import ToggleOnSVG from '../../public/images/buttons/toggle_on.svg';
import ToggleOffSVG from '../../public/images/buttons/toggle_off.svg';
import FullScreenSVG from '../../public/images/buttons/full_screen.svg';


export enum Icon {
BACK,
Expand All @@ -25,6 +27,7 @@ export enum Icon {
VOLUME_DOWN,
MORE,
CAMERA,
FULL_SCREEN,
KEYBOARD,
CANCEL,
OFFLINE,
Expand All @@ -40,6 +43,8 @@ export default class SvgImage {
static Icon = Icon;
private static getSvgString(type: Icon): string {
switch (type) {
case Icon.FULL_SCREEN:
return FullScreenSVG
case Icon.KEYBOARD:
return KeyboardSVG;
case Icon.MORE:
Expand Down
9 changes: 9 additions & 0 deletions src/public/images/buttons/full_screen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.