Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
DevOps committed Dec 19, 2024
2 parents 7fd82ba + 4f499a1 commit 52f3b32
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 233 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
git merge -X theirs --no-ff ${{ github.event.inputs.source }}
- name: Update package versions
run: |
if [ "${{ github.event.inputs.branch }}" == "master" ]; then
if [ "${{ github.event.inputs.branch }}" == "master" ] || [[ "${{ github.event.inputs.branch }}" =~ ^release/.* ]]; then
VERSION=$("./script/next_version.sh")
else
VERSION=$("./script/next_version.sh" "${GITHUB_RUN_ID}")
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
run: |
npm run build
- name: Publish Master NPM Packages
if: ${{ github.ref == 'refs/heads/master' }}
if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release/') }}
run: |
npm whoami
echo "Publishing NPM Packages..."
Expand All @@ -63,7 +63,7 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish Alpha NPM Packages
if: ${{ github.ref != 'refs/heads/master' }}
if: ${{ github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/heads/release/') }}
run: |
npm whoami
echo "Publishing NPM Packages..."
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ server_bundle_stats.json
web_bundle_stats.json
junit.xml
ecosystem.json
.npmrc
*.tsbuildinfo
*.d.ts.map
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
prefer-workspace-packages=true
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# CasualOS Changelog

## V3.3.15

#### Date: 12/19/2024

### :rocket: Features

- Added the ability to use CasualOS URLs in `<video>` HTML custom app elements.
- This makes it possible to use LiveKit tracks in a custom app.
- Tip: Utilize the `autoplay` attribute to automatically play video from a track.

### :bug: Bug Fixes

- Fixed an issue in `LivekitManager` where media permissions for the camera and microphone were not requested before joining a room. Resulting in silent fails for Google Chrome users. The `joinRoom` method now includes a preliminary step to check and request these permissions, ensuring a smoother user experience.

## V3.3.14

#### Date: 12/3/2024
Expand Down
48 changes: 48 additions & 0 deletions src/aux-server/aux-web/shared/MediaUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { hasValue } from '@casual-simulation/aux-common';
import { appManager } from './AppManager';
import { ParsedCasualOSUrl } from './UrlUtils';

/**
* Gets the media for the given CasualOS URL.
* Returns null if the media could not be found.
* @param url The URL that should be used to get the media.
*/
export async function getMediaForCasualOSUrl(
url: ParsedCasualOSUrl | null
): Promise<MediaProvider> {
if (url && url.type === 'camera-feed') {
try {
const media = await window.navigator.mediaDevices.getUserMedia({
audio: false,
video: {
// Use the user specified one if specified.
// Otherwise default to environment.
facingMode: hasValue(url.camera)
? {
exact:
url.camera === 'front'
? 'user'
: 'environment',
}
: { ideal: 'environment' },
},
});

return media;
} catch (err) {
console.warn(
'[Game] Unable to get camera feed for background.',
err
);
return;
}
} else if (url && url.type === 'video-element') {
for (let sim of appManager.simulationManager.simulations.values()) {
let stream = sim.livekit.getMediaByAddress(url.address);
if (stream) {
return stream;
}
}
}
return null;
}
90 changes: 90 additions & 0 deletions src/aux-server/aux-web/shared/UrlUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { parseCasualOSUrl } from './UrlUtils';

describe('parseCasualOSUrl()', () => {
it('should return an object describing the CasualOS URL', () => {
expect(parseCasualOSUrl('casualos://camera-feed')).toEqual({
type: 'camera-feed',
});

expect(parseCasualOSUrl('casualos://camera-feed/front')).toEqual({
type: 'camera-feed',
camera: 'front',
});

expect(parseCasualOSUrl('casualos://camera-feed/rear')).toEqual({
type: 'camera-feed',
camera: 'rear',
});

expect(parseCasualOSUrl('casualos://camera-feed/other')).toEqual({
type: 'camera-feed',
});

expect(
parseCasualOSUrl('casualos://video-element/uuid-123-abc')
).toEqual({
type: 'video-element',
address: 'casualos://video-element/uuid-123-abc',
});
});

// See https://bugs.chromium.org/p/chromium/issues/detail?id=869291
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1374505
it('should support Chrome and Firefox URL results', () => {
// How Chrome/Firefox parse casualos://camera-feed
expect(
parseCasualOSUrl({
protocol: 'casualos:',
hostname: '',
host: '',
pathname: '//camera-feed',
})
).toEqual({
type: 'camera-feed',
});

// How Chrome/Firefox parse casualos://camera-feed/front
expect(
parseCasualOSUrl({
protocol: 'casualos:',
hostname: '',
host: '',
pathname: '//camera-feed/front',
})
).toEqual({
type: 'camera-feed',
camera: 'front',
});

// How Chrome/Firefox parse casualos://camera-feed/rear
expect(
parseCasualOSUrl({
protocol: 'casualos:',
hostname: '',
host: '',
pathname: '//camera-feed/rear',
})
).toEqual({
type: 'camera-feed',
camera: 'rear',
});

// How Chrome/Firefox parse casualos://video-element/uuid-123-abc
expect(
parseCasualOSUrl({
protocol: 'casualos:',
hostname: '',
host: '',
pathname: '//video-element/uuid-123-abc',
href: 'casualos://video-element/uuid-123-abc',
})
).toEqual({
type: 'video-element',
address: 'casualos://video-element/uuid-123-abc',
});
});

it('should return null if given a non CasualOS URL', () => {
expect(parseCasualOSUrl('http://example.com')).toBe(null);
});
});
90 changes: 90 additions & 0 deletions src/aux-server/aux-web/shared/UrlUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { CameraType } from '@casual-simulation/aux-common';

export type ParsedCasualOSUrl = CasualOSCameraFeedUrl | CasualOSVideoElementUrl;

export interface CasualOSCameraFeedUrl {
type: 'camera-feed';
camera?: CameraType;
}

export interface CasualOSVideoElementUrl {
type: 'video-element';
address: string;
}

/**
* Parses special CasualOS URLs into an object that indicates what it should be used for.
* Currently only supports "casualos://camera-feed/{rear|front}".
* Returns null if the URL has no special CasualOS meaning.
* @param url The URL that should be parsed.
* @returns
*/
export function parseCasualOSUrl(
url: string | Partial<URL>
): ParsedCasualOSUrl {
try {
const uri = typeof url === 'object' ? url : new URL(url);
if (uri.protocol !== 'casualos:') {
return null;
}

if (uri.hostname === 'camera-feed') {
let camera: CameraType;
if (uri.pathname === '/front') {
camera = 'front';
} else if (uri.pathname === '/rear') {
camera = 'rear';
}

let result: ParsedCasualOSUrl = {
type: 'camera-feed',
};

if (camera) {
result.camera = camera;
}

return result;
} else if (uri.hostname === 'video-element') {
let result: ParsedCasualOSUrl = {
type: 'video-element',
address: uri.href,
};

return result;
} else if (uri.hostname === '') {
// Chrome/Firefox
// See https://bugs.chromium.org/p/chromium/issues/detail?id=869291 and https://bugzilla.mozilla.org/show_bug.cgi?id=1374505
if (uri.pathname.startsWith('//camera-feed')) {
let path = uri.pathname.slice('//camera-feed'.length);
let camera: CameraType;
if (path === '/front') {
camera = 'front';
} else if (path === '/rear') {
camera = 'rear';
}

let result: ParsedCasualOSUrl = {
type: 'camera-feed',
};

if (camera) {
result.camera = camera;
}

return result;
} else if (uri.pathname.startsWith('//video-element')) {
let result: ParsedCasualOSUrl = {
type: 'video-element',
address: uri.href,
};

return result;
}
}

return null;
} catch {
return null;
}
}
2 changes: 1 addition & 1 deletion src/aux-server/aux-web/shared/ab1/prod/abShell.aux

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/aux-server/aux-web/shared/scene/AuxTextureLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import {
Loader,
VideoTexture,
} from '@casual-simulation/three';
import { parseCasualOSUrl, addCorsQueryParam } from './SceneUtils';
import { addCorsQueryParam } from './SceneUtils';
import { appManager } from '../AppManager';
import { parseCasualOSUrl } from '../UrlUtils';

// TODO: Put a max size on the cache.
const cache = new Map<string, Promise<Texture>>();
Expand Down
58 changes: 10 additions & 48 deletions src/aux-server/aux-web/shared/scene/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ import { TweenCameraToOperation } from '../interaction/TweenCameraToOperation';
import {
baseAuxAmbientLight,
baseAuxDirectionalLight,
parseCasualOSUrl,
WORLD_UP,
ParsedCasualOSUrl,
TweenCameraPosition,
} from './SceneUtils';
import { createHtmlMixerContext, disposeHtmlMixerContext } from './HtmlUtils';
Expand All @@ -86,6 +84,8 @@ import { update as updateMeshUI } from 'three-mesh-ui';
import { EXRLoader } from '@casual-simulation/three/examples/jsm/loaders/EXRLoader';
import Bowser from 'bowser';
import { EnableXRModalRequestParameters } from '../vue-components/EnableXRModal/EnableXRModal';
import { getMediaForCasualOSUrl } from '../MediaUtils';
import { parseCasualOSUrl } from '../UrlUtils';

export const PREFERRED_XR_REFERENCE_SPACE = 'local-floor';

Expand Down Expand Up @@ -797,8 +797,15 @@ export abstract class Game {
}

this.gameView.gameBackground.prepend(this._backgroundVideoElement);
const media = await this._getMediaForCasualOSUrl(casualOSUrl);
const media = await getMediaForCasualOSUrl(casualOSUrl);
if (media) {
this._backgroundVideoSubscription = new Subscription(() => {
if (media instanceof MediaStream) {
for (let track of media.getTracks()) {
track.stop();
}
}
});
this._backgroundVideoElement.srcObject = media;
} else {
this._backgroundVideoElement.src = address;
Expand All @@ -807,51 +814,6 @@ export abstract class Game {
}
}

private async _getMediaForCasualOSUrl(
url: ParsedCasualOSUrl
): Promise<MediaProvider> {
if (url && url.type === 'camera-feed') {
try {
const media = await window.navigator.mediaDevices.getUserMedia({
audio: false,
video: {
// Use the user specified one if specified.
// Otherwise default to environment.
facingMode: hasValue(url.camera)
? {
exact:
url.camera === 'front'
? 'user'
: 'environment',
}
: { ideal: 'environment' },
},
});
this._backgroundVideoSubscription = new Subscription(() => {
for (let track of media.getTracks()) {
track.stop();
}
});

return media;
} catch (err) {
console.warn(
'[Game] Unable to get camera feed for background.',
err
);
return;
}
} else if (url && url.type === 'video-element') {
for (let sim of appManager.simulationManager.simulations.values()) {
let stream = sim.livekit.getMediaByAddress(url.address);
if (stream) {
return stream;
}
}
}
return null;
}

protected _resizeBackgroundVideoElement() {
if (!this._backgroundVideoElement) {
return;
Expand Down
Loading

0 comments on commit 52f3b32

Please sign in to comment.