Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/editor/src/ui/presenter/PresenterView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,7 @@ export function PresenterView({ sessionId = getRouteSessionId() }: PresenterView
project={snapshot.project}
activePageId={activePage.id}
selection={{ ...emptySelection, pageId: activePage.id }}
hideReadOnlyMediaPlaceholder
presentationMode
readOnly
zoomPercent={100}
Expand Down
67 changes: 67 additions & 0 deletions apps/editor/tests/unit/ui/presenter/PresenterView.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import Konva from 'konva';
import { StrictMode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { sampleProject } from '../../../../src/domain/projects/sampleProject';
Expand Down Expand Up @@ -161,6 +162,72 @@ describe('PresenterView', () => {
expect(notes.style.fontSize).not.toBe(initialSize);
});

it('renders video frames instead of the read-only filename placeholder', () => {
window.localStorage.setItem('localstudio.presenterWindowIntroDismissed', '1');
render(<PresenterView sessionId="session-1" />);

const project = sampleProject.createSampleProject();
project.assets['presenter-video-asset'] = {
id: 'presenter-video-asset',
mimeType: 'video/mp4',
name: 'Presenter video',
objectUrl: 'blob:presenter-video',
type: 'video',
};
project.elements['presenter-video'] = {
assetId: 'presenter-video-asset',
autoplayInPreview: false,
controls: false,
height: 360,
id: 'presenter-video',
locked: false,
loop: false,
muted: true,
opacity: 1,
rotation: 0,
trimStartSeconds: 0,
type: 'video',
visible: true,
volume: 1,
width: 640,
x: 120,
y: 80,
};
project.pages[0]!.elementIds.push('presenter-video');

act(() => {
window.dispatchEvent(
new MessageEvent('message', {
origin: window.location.origin,
data: {
payload: {
activePageId: project.pages[0]!.id,
animationPreview: undefined,
project,
},
sessionId: 'session-1',
source: 'localstudio-presenter-main',
type: 'state',
},
}),
);
});

const currentSlide = screen.getByRole('region', { name: 'Current slide' });
const video = currentSlide.querySelector(
'video[aria-label="Presenter video"]',
) as HTMLVideoElement;
expect(video).toBeInTheDocument();

const presenterStage = Konva.stages.find((stage) =>
currentSlide.contains(stage.container()),
);
const videoNode = presenterStage
?.find('Image')
.find((node) => (node as Konva.Image).image() === video);
expect(videoNode).toBeDefined();
});

it('resizes presenter notes with the divider drag handle', () => {
window.localStorage.setItem('localstudio.presenterWindowIntroDismissed', '1');
Object.defineProperty(window, 'innerWidth', {
Expand Down
29 changes: 28 additions & 1 deletion tests/e2e/editor/presenter-keyboard-video.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { runPresenterKeyboardVideoJourney } from './presenter-keyboard-video-journey';
import { test, withIsolatedDevServer } from '../support/journey-test';
import { EditorAppPage } from '../pages/editor-app.page';
import { expect, test, withIsolatedDevServer } from '../support/journey-test';
import { presenterKeyboardVideoSetup } from './presenter-keyboard-video-setup';
import { presenterNotesWindow } from './presenter-notes-window';
import { seekPresenterVideoFrame } from './presenter-video-frame-seek-browser';

const getServer = withIsolatedDevServer(test);

Expand All @@ -8,3 +12,26 @@ test('presents from the editor and controls slides and video with keyboard short
}) => {
await runPresenterKeyboardVideoJourney(page, getServer().baseURL);
});

test('renders changing video frames in the presenter viewer', async ({ page }) => {
await presenterKeyboardVideoSetup.installFullscreenMock(page);
const editor = new EditorAppPage(page, getServer().baseURL);
await editor.gotoNewProject();
await presenterKeyboardVideoSetup.addVideoAndSecondSlide(editor, page);

const presenterPage = await presenterNotesWindow.open(page);
const currentSlide = presenterPage.getByRole('region', { name: 'Current slide' });
const video = currentSlide.locator(
'video.canvas-media-element[aria-label="Big_Buck_Bunny_360_10s_1MB.mp4"]',
);
await expect(video).toBeVisible();

async function captureFrame(time: number) {
await video.evaluate(seekPresenterVideoFrame, time);
return video.screenshot();
}

const firstFrame = await captureFrame(1);
const secondFrame = await captureFrame(5);
expect(firstFrame.equals(secondFrame)).toBe(false);
});
18 changes: 18 additions & 0 deletions tests/e2e/editor/presenter-video-frame-seek-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export async function seekPresenterVideoFrame(
element: Element,
targetTime: number,
): Promise<void> {
const movie = element as HTMLVideoElement;
movie.pause();
await new Promise<void>((resolve) => {
if (Math.abs(movie.currentTime - targetTime) < 0.01) {
resolve();
return;
}
movie.addEventListener('seeked', () => resolve(), { once: true });
movie.currentTime = targetTime;
});
await new Promise<void>((resolve) => {
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
});
}
Loading