Skip to content

Commit

Permalink
Add support for media video parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
keianhzo committed Jun 2, 2023
1 parent 39b189a commit 5f5c86c
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ export const MediaPDF = defineComponent({
MediaPDF.map = new Map();

export const MediaVideo = defineComponent({
autoPlay: Types.ui8,
ratio: Types.f32
ratio: Types.f32,
flags: Types.ui8
});
/**
* @type {Map<EntityId, HTMLVideoElement}>}
Expand Down
20 changes: 14 additions & 6 deletions src/bit-systems/media-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,28 @@ export function* waitForMediaLoaded(world: HubsWorld, eid: EntityID) {
const loaderForMediaType = {
[MediaType.IMAGE]: (
world: HubsWorld,
eid: EntityID,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadImage(world, accessibleUrl, contentType),
[MediaType.VIDEO]: (
world: HubsWorld,
eid: EntityID,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadVideo(world, accessibleUrl, contentType),
) => loadVideo(world, eid, accessibleUrl, contentType),
[MediaType.MODEL]: (
world: HubsWorld,
eid: EntityID,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadModel(world, accessibleUrl, contentType, true),
[MediaType.PDF]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) => loadPDF(world, accessibleUrl),
[MediaType.AUDIO]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) => loadAudio(world, accessibleUrl),
[MediaType.HTML]: (world: HubsWorld, { canonicalUrl, thumbnail }: { canonicalUrl: string, thumbnail: string }) =>
loadHtml(world, canonicalUrl, thumbnail)
[MediaType.PDF]: (world: HubsWorld, eid: EntityID, { accessibleUrl }: { accessibleUrl: string }) =>
loadPDF(world, accessibleUrl),
[MediaType.AUDIO]: (world: HubsWorld, eid: EntityID, { accessibleUrl }: { accessibleUrl: string }) =>
loadAudio(world, eid, accessibleUrl),
[MediaType.HTML]: (
world: HubsWorld,
eid: EntityID,
{ canonicalUrl, thumbnail }: { canonicalUrl: string; thumbnail: string }
) => loadHtml(world, canonicalUrl, thumbnail)
};

export const MEDIA_LOADER_FLAGS = {
Expand Down Expand Up @@ -191,7 +199,7 @@ function* loadMedia(world: HubsWorld, eid: EntityID) {
if (!loader) {
throw new UnsupportedMediaTypeError(eid, urlData.mediaType);
}
media = yield* loader(world, urlData);
media = yield* loader(world, eid, urlData);
addComponent(world, MediaLoaded, media);
} catch (e) {
console.error(e);
Expand Down
15 changes: 9 additions & 6 deletions src/bit-systems/video-menu-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { coroutine } from "../utils/coroutine";
import { easeOutQuadratic } from "../utils/easing";
import { isFacingCamera } from "../utils/three-utils";
import { Emitter2Audio } from "./audio-emitter-system";
import { VIDEO_FLAGS } from "../inflators/video";

const videoMenuQuery = defineQuery([VideoMenu]);
const hoverRightVideoQuery = defineQuery([HoveredRemoteRight, MediaVideo]);
Expand Down Expand Up @@ -71,12 +72,14 @@ export function videoMenuSystem(world: HubsWorld, userinput: any) {
}

hoverRightVideoEnterQuery(world).forEach(function (eid) {
const menu = rightVideoMenu;
VideoMenu.videoRef[menu] = eid;
const menuObj = world.eid2obj.get(menu)!;
const videoObj = world.eid2obj.get(eid)!;
videoObj.add(menuObj);
setCursorRaycastable(world, menu, true);
if (MediaVideo.flags[eid] & VIDEO_FLAGS.CONTROLS) {
const menu = rightVideoMenu;
VideoMenu.videoRef[menu] = eid;
const menuObj = world.eid2obj.get(menu)!;
const videoObj = world.eid2obj.get(eid)!;
videoObj.add(menuObj);
setCursorRaycastable(world, menu, true);
}
});

videoMenuQuery(world).forEach(function (eid) {
Expand Down
3 changes: 2 additions & 1 deletion src/bit-systems/video-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AudioSystem } from "../systems/audio-system";
import { findAncestorWithComponent } from "../utils/bit-utils";
import { Emitter2Audio, Emitter2Params, makeAudioEntity } from "./audio-emitter-system";
import { takeSoftOwnership } from "../utils/take-soft-ownership";
import { VIDEO_FLAGS } from "../inflators/video";

enum Flags {
PAUSED = 1 << 0
Expand All @@ -33,7 +34,7 @@ export function videoSystem(world: HubsWorld, audioSystem: AudioSystem) {
mediaVideoEnterQuery(world).forEach(function (videoEid) {
const videoObj = world.eid2obj.get(videoEid) as Mesh;
const video = MediaVideoData.get(videoEid)!;
if (MediaVideo.autoPlay[videoEid]) {
if (MediaVideo.flags[videoEid] & VIDEO_FLAGS.AUTOPLAY) {
video.play().catch(() => {
// Need to deal with the fact play() may fail if user has not interacted with browser yet.
console.error("Error auto-playing video.");
Expand Down
12 changes: 11 additions & 1 deletion src/inflators/video-loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { HubsWorld } from "../app";
import { MediaVideo } from "../bit-components";
import { ProjectionMode } from "../utils/projection-mode";
import { inflateMediaLoader } from "./media-loader";
import { VIDEO_FLAGS } from "./video";

export interface VideoLoaderParams {
src: string;
Expand All @@ -19,5 +21,13 @@ export function inflateVideoLoader(world: HubsWorld, eid: number, params: VideoL
isObjectMenuTarget: false
});

// TODO: Use the rest of VideoLoaderParams
const { autoPlay, controls, loop, projection } = params;
autoPlay && (MediaVideo.flags[eid] |= VIDEO_FLAGS.AUTOPLAY);
loop && (MediaVideo.flags[eid] |= VIDEO_FLAGS.LOOP);
controls && (MediaVideo.flags[eid] |= VIDEO_FLAGS.CONTROLS);
if (projection === ProjectionMode.SPHERE_EQUIRECTANGULAR) {
MediaVideo.flags[eid] |= VIDEO_FLAGS.PROJECTION_EQUIRECT;
} else {
MediaVideo.flags[eid] |= VIDEO_FLAGS.PROJECTION_FLAT;
}
}
18 changes: 0 additions & 18 deletions src/inflators/video.js

This file was deleted.

49 changes: 49 additions & 0 deletions src/inflators/video.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { create360ImageMesh, createImageMesh } from "../utils/create-image-mesh";
import { addComponent } from "bitecs";
import { addObject3DComponent } from "../utils/jsx-entity";
import { ProjectionMode } from "../utils/projection-mode";
import { MediaVideo, MediaVideoData } from "../bit-components";
import { HubsWorld } from "../app";
import { EntityID } from "../utils/networking-types";
import { Texture } from "three";

export const VIDEO_FLAGS = {
AUTOPLAY: 1 << 0,
LOOP: 1 << 1,
CONTROLS: 1 << 2,
PROJECTION_FLAT: 1 << 3,
PROJECTION_EQUIRECT: 1 << 4
};

export interface VideoParams {
texture: Texture;
ratio: number;
projection: ProjectionMode;
autoPlay: boolean;
video: HTMLVideoElement;
loop: boolean;
controls: boolean;
}

export function inflateVideo(world: HubsWorld, eid: EntityID, params: VideoParams) {
const { texture, ratio, projection, autoPlay, video, loop, controls } = params;
const mesh =
projection === ProjectionMode.SPHERE_EQUIRECTANGULAR
? create360ImageMesh(texture)
: createImageMesh(texture, ratio);
addObject3DComponent(world, eid, mesh);
addComponent(world, MediaVideo, eid);

autoPlay && (MediaVideo.flags[eid] |= VIDEO_FLAGS.AUTOPLAY);
loop && (MediaVideo.flags[eid] |= VIDEO_FLAGS.LOOP);
controls && (MediaVideo.flags[eid] |= VIDEO_FLAGS.CONTROLS);
if (projection === ProjectionMode.SPHERE_EQUIRECTANGULAR) {
MediaVideo.flags[eid] |= VIDEO_FLAGS.PROJECTION_EQUIRECT;
} else {
MediaVideo.flags[eid] |= VIDEO_FLAGS.PROJECTION_FLAT;
}

MediaVideo.ratio[eid] = ratio;
MediaVideoData.set(eid, video);
return eid;
}
10 changes: 2 additions & 8 deletions src/utils/jsx-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { inflateMediaLoader } from "../inflators/media-loader";
import { inflateMediaFrame } from "../inflators/media-frame";
import { GrabbableParams, inflateGrabbable } from "../inflators/grabbable";
import { inflateImage } from "../inflators/image";
import { inflateVideo } from "../inflators/video";
import { inflateVideo, VideoParams } from "../inflators/video";
import { inflateModel, ModelParams } from "../inflators/model";
import { inflatePDFLoader, PDFLoaderParams } from "../inflators/pdf-loader";
import { inflateVideoLoader, VideoLoaderParams } from "../inflators/video-loader";
Expand Down Expand Up @@ -275,13 +275,7 @@ export interface JSXComponentData extends ComponentData {
alphaMode: typeof AlphaMode.Blend | typeof AlphaMode.Mask | typeof AlphaMode.Opaque;
cacheKey: string;
};
video?: {
texture: HubsVideoTexture;
ratio: number;
projection: ProjectionMode;
autoPlay: boolean;
video: HTMLVideoElement;
};
video?: VideoParams;
networkedVideo?: true;
videoMenu?: {
sliderRef: Ref;
Expand Down
6 changes: 5 additions & 1 deletion src/utils/load-audio-texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { HubsVideoTexture } from "../textures/HubsVideoTexture";

// TODO: Replace async with function*?
export async function loadAudioTexture(
src: string
src: string,
loop: boolean,
autoplay: boolean
): Promise<{ texture: HubsVideoTexture; ratio: number; video: HTMLVideoElement }> {
const videoEl = createVideoOrAudioEl("video") as HTMLVideoElement;
const imageEl = new Image();
Expand Down Expand Up @@ -33,6 +35,8 @@ export async function loadAudioTexture(

videoEl.src = src;
videoEl.onerror = failLoad;
videoEl.loop = loop;
videoEl.autoplay = autoplay;

// NOTE: We used to use the canplay event here to yield the texture, but that fails to fire on iOS Safari
// and also sometimes in Chrome it seems.
Expand Down
29 changes: 24 additions & 5 deletions src/utils/load-audio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,27 @@ import { renderAsEntity } from "../utils/jsx-entity";
import { loadAudioTexture } from "../utils/load-audio-texture";
import { HubsWorld } from "../app";
import { HubsVideoTexture } from "../textures/HubsVideoTexture";
import { EntityID } from "./networking-types";
import { MediaVideo } from "../bit-components";
import { VIDEO_FLAGS } from "../inflators/video";

export function* loadAudio(world: HubsWorld, eid: EntityID, url: string) {
let loop = true;
let autoPlay = true;
let controls = true;
let projection = ProjectionMode.FLAT;
if (MediaVideo.flags[eid]) {
autoPlay = (MediaVideo.flags[eid] & VIDEO_FLAGS.AUTOPLAY) !== 0;
loop = (MediaVideo.flags[eid] & VIDEO_FLAGS.LOOP) !== 0;
controls = (MediaVideo.flags[eid] & VIDEO_FLAGS.CONTROLS) !== 0;
projection =
(MediaVideo.flags[eid] & VIDEO_FLAGS.PROJECTION_EQUIRECT) !== 0
? ProjectionMode.SPHERE_EQUIRECTANGULAR
: ProjectionMode.FLAT;
}

export function* loadAudio(world: HubsWorld, url: string) {
const { texture, ratio, video }: { texture: HubsVideoTexture; ratio: number; video: HTMLVideoElement } =
yield loadAudioTexture(url);
yield loadAudioTexture(url, loop, autoPlay);

return renderAsEntity(
world,
Expand All @@ -22,9 +39,11 @@ export function* loadAudio(world: HubsWorld, url: string) {
video={{
texture,
ratio,
autoPlay: true,
projection: ProjectionMode.FLAT,
video
autoPlay,
projection,
video,
loop,
controls
}}
></entity>
);
Expand Down
4 changes: 3 additions & 1 deletion src/utils/load-video-texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { HLSVideoTexture } from "../textures/HLSVideoTexture";
import { createDashPlayer, createHLSPlayer, createVideoOrAudioEl } from "./media-utils";
import { HubsVideoTexture } from "../textures/HubsVideoTexture";

export async function loadVideoTexture(src, contentType) {
export async function loadVideoTexture(src, contentType, loop, autoplay) {
const videoEl = createVideoOrAudioEl("video");
let texture = null;

Expand Down Expand Up @@ -48,6 +48,8 @@ export async function loadVideoTexture(src, contentType) {
texture = new HubsVideoTexture(videoEl);
videoEl.src = src;
videoEl.onerror = failLoad;
videoEl.loop = loop;
videoEl.autoplay = autoplay;
}

texture.minFilter = LinearFilter;
Expand Down
29 changes: 24 additions & 5 deletions src/utils/load-video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,27 @@ import { renderAsEntity } from "../utils/jsx-entity";
import { loadVideoTexture } from "../utils/load-video-texture";
import { HubsWorld } from "../app";
import { HubsVideoTexture } from "../textures/HubsVideoTexture";
import { EntityID } from "./networking-types";
import { MediaVideo } from "../bit-components";
import { VIDEO_FLAGS } from "../inflators/video";

export function* loadVideo(world: HubsWorld, eid: EntityID, url: string, contentType: string) {
let loop = true;
let autoPlay = true;
let controls = true;
let projection = ProjectionMode.FLAT;
if (MediaVideo.flags[eid]) {
autoPlay = (MediaVideo.flags[eid] & VIDEO_FLAGS.AUTOPLAY) !== 0;
loop = (MediaVideo.flags[eid] & VIDEO_FLAGS.LOOP) !== 0;
controls = (MediaVideo.flags[eid] & VIDEO_FLAGS.CONTROLS) !== 0;
projection =
(MediaVideo.flags[eid] & VIDEO_FLAGS.PROJECTION_EQUIRECT) !== 0
? ProjectionMode.SPHERE_EQUIRECTANGULAR
: ProjectionMode.FLAT;
}

export function* loadVideo(world: HubsWorld, url: string, contentType: string) {
const { texture, ratio, video }: { texture: HubsVideoTexture; ratio: number; video: HTMLVideoElement } =
yield loadVideoTexture(url, contentType);
yield loadVideoTexture(url, contentType, loop, autoPlay);

return renderAsEntity(
world,
Expand All @@ -20,9 +37,11 @@ export function* loadVideo(world: HubsWorld, url: string, contentType: string) {
video={{
texture,
ratio,
autoPlay: true,
projection: ProjectionMode.FLAT,
video
autoPlay,
projection,
video,
loop,
controls
}}
></entity>
);
Expand Down

0 comments on commit 5f5c86c

Please sign in to comment.