Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): chromecast support for shared content #14497

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import CastSender from '$lib/components/cast/cast-sender.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AppRoute } from '$lib/constants';
Expand Down Expand Up @@ -83,6 +84,7 @@
const sharedLink = getSharedLink();
let isOwner = $derived($user && asset.ownerId === $user?.id);
let showDownloadButton = $derived(sharedLink ? sharedLink.allowDownload : !asset.isOffline);
let showCastButton = $derived(sharedLink && !sharedLink.password && window.chrome);
// $: showEditorButton =
// isOwner &&
// asset.type === AssetTypeEnum.Image &&
Expand Down Expand Up @@ -133,10 +135,13 @@
{#if showDetailButton}
<ShowDetailAction {onShowDetail} />
{/if}

{#if isOwner}
<FavoriteAction {asset} {onAction} />
{/if}
{#if showCastButton}
<CastSender {asset} />
{/if}

<!-- {#if showEditorButton}
<CircleIconButton
color="opaque"
Expand Down
76 changes: 76 additions & 0 deletions web/src/lib/components/cast/cast-sender.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { getKey } from '$lib/utils';
import { getBaseUrl, type AssetResponseDto } from '@immich/sdk';
import { mdiCast } from '@mdi/js';
import { onMount } from 'svelte';

interface Props {
asset: AssetResponseDto;
}

let { asset, ...rest }: Props = $props();

let key = getKey();
let downloadUrl = window.location.origin + getBaseUrl() + `/assets/${asset.id}/original` + (key ? `?key=${key}` : '');
let thumbUrl =
window.location.origin + getBaseUrl() + `/assets/${asset.id}/thumbnail?size=thumbnail` + (key ? `&key=${key}` : '');

const __onGCastApiAvailable = function (isAvailable: boolean) {
if (isAvailable) {
initializeCastApi();
}
};

onMount(() => {
if (window.chrome && !window.chrome.cast) {
window['__onGCastApiAvailable'] = __onGCastApiAvailable;

const script = document.createElement('script');
script.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
script.onload = () => {
console.log('loaded cast from gstatic');
};
script.onerror = () => {
console.error('failed to load cast from gstatic.');
};
document.body.appendChild(script);
}
});

const initializeCastApi = function () {
var options = {};

// use the chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
options.receiverApplicationId = chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID;

// ORIGIN_SCOPED - Auto connect from same appId and page origin
options.autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED;

cast.framework.CastContext.getInstance().setOptions(options);
};

const castAsset = async () => {
// get or create session
var castSession = cast.framework.CastContext.getInstance().getCurrentSession();
if (!castSession) {
await cast.framework.CastContext.getInstance().requestSession();
castSession = cast.framework.CastContext.getInstance().getCurrentSession();
}

var mediaInfo = new chrome.cast.media.MediaInfo(downloadUrl, asset.originalMimeType);

// for thumb and title
mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata();
mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC;
mediaInfo.metadata.title = asset.originalFileName;
mediaInfo.metadata.images = [{ url: thumbUrl }];

var request = new chrome.cast.media.LoadRequest(mediaInfo);

// cast content
await castSession.loadMedia(request);
};
</script>

<CircleIconButton onclick={castAsset} color="opaque" hideMobile={false} icon={mdiCast} title={'cast'} />
Loading