From a930eab8d2bf9970c0a563318c36acb10b7a2b62 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Sun, 1 Oct 2023 19:04:01 -0700 Subject: [PATCH] refactor: split nativex Signed-off-by: Varun Patil --- src/components/Settings.vue | 8 +- src/components/viewer/Viewer.vue | 2 +- src/native.ts | 368 ------------------------------- src/native/api.ts | 165 ++++++++++++++ src/native/basic.ts | 51 +++++ src/native/config.ts | 23 ++ src/native/days.ts | 75 +++++++ src/native/index.ts | 6 + src/native/share.ts | 45 ++++ src/native/video.ts | 20 ++ src/services/utils/helpers.ts | 4 +- 11 files changed, 392 insertions(+), 375 deletions(-) delete mode 100644 src/native.ts create mode 100644 src/native/api.ts create mode 100644 src/native/basic.ts create mode 100644 src/native/config.ts create mode 100644 src/native/days.ts create mode 100644 src/native/index.ts create mode 100644 src/native/share.ts create mode 100644 src/native/video.ts diff --git a/src/components/Settings.vue b/src/components/Settings.vue index 38d83777..2a3bb8dd 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -288,12 +288,12 @@ export default defineComponent({ }, // --------------- Native APIs start ----------------------------- - async refreshNativeConfig() { - this.localFolders = await nativex.getLocalFolders(); + refreshNativeConfig() { + this.localFolders = nativex.getLocalFolders(); }, - async updateDeviceFolders() { - await nativex.setLocalFolders(this.localFolders); + updateDeviceFolders() { + nativex.setLocalFolders(this.localFolders); }, async logout() { diff --git a/src/components/viewer/Viewer.vue b/src/components/viewer/Viewer.vue index 7717e558..b1a6bd4c 100644 --- a/src/components/viewer/Viewer.vue +++ b/src/components/viewer/Viewer.vue @@ -807,7 +807,7 @@ export default defineComponent({ if (!isvideo) { // Try local file if NativeX is available if (photo.auid && nativex.has()) { - highSrc.push(nativex.API.IMAGE_FULL(photo.auid)); + highSrc.push(nativex.NAPI.IMAGE_FULL(photo.auid)); } // Decodable full resolution image diff --git a/src/native.ts b/src/native.ts deleted file mode 100644 index cf5a3173..00000000 --- a/src/native.ts +++ /dev/null @@ -1,368 +0,0 @@ -import axios from '@nextcloud/axios'; -import { generateUrl } from '@nextcloud/router'; -import type { IDay, IPhoto } from './types'; -import { API as SAPI } from './services/API'; -const euc = encodeURIComponent; - -/** Access NativeX over localhost */ -const BASE_URL = 'http://127.0.0.1'; - -/** NativeX asynchronous API */ -export const API = { - /** - * Local days API. - * @regex ^/api/days$ - * @returns {IDay[]} for all locally available days. - */ - DAYS: () => `${BASE_URL}/api/days`, - /** - * Local photos API. - * @regex ^/api/days/\d+$ - * @param dayId Day ID to fetch photos for - * @returns {IPhoto[]} for all locally available photos for this day. - */ - DAY: (dayId: number) => `${BASE_URL}/api/days/${dayId}`, - - /** - * Local photo metadata API. - * @regex ^/api/image/info/\d+$ - * @param fileId File ID of the photo - * @returns {IImageInfo} for the given file ID (local). - */ - IMAGE_INFO: (fileId: number) => `${BASE_URL}/api/image/info/${fileId}`, - - /** - * Delete files using local fileids. - * @regex ^/api/image/delete/\d+(,\d+)*$ - * @param fileIds List of AUIDs to delete - * @param dry (Query) Only check for confirmation and count of local files - * @returns {void} - * @throws Return an error code if the user denies the deletion. - */ - IMAGE_DELETE: (auids: number[]) => `${BASE_URL}/api/image/delete/${auids.join(',')}`, - - /** - * Local photo preview API. - * @regex ^/image/preview/\d+$ - * @param fileId File ID of the photo - * @returns {Blob} JPEG preview of the photo. - */ - IMAGE_PREVIEW: (fileId: number) => `${BASE_URL}/image/preview/${fileId}`, - /** - * Local photo full API. - * @regex ^/image/full/\d+$ - * @param auid AUID of the photo - * @returns {Blob} JPEG full image of the photo. - */ - IMAGE_FULL: (auid: number) => `${BASE_URL}/image/full/${auid}`, - - /** - * Share a URL with native page. - * The native client MUST NOT download the object but share the URL directly. - * @regex ^/api/share/url/.+$ - * @param url URL to share (double-encoded) - * @returns {void} - */ - SHARE_URL: (url: string) => `${BASE_URL}/api/share/url/${euc(euc(url))}`, - /** - * Share an object (as blob) natively using a given URL. - * The native client MUST download the object using a download manager - * and immediately prompt the user to download it. The asynchronous call - * must return only after the object has been downloaded. - * @regex ^/api/share/blob/.+$ - * @param url URL to share (double-encoded) - * @returns {void} - */ - SHARE_BLOB: (url: string) => `${BASE_URL}/api/share/blob/${euc(euc(url))}`, - /** - * Share a local file (as blob) with native page. - * @regex ^/api/share/local/\d+$ - * @param auid AUID of the photo - * @returns {void} - */ - SHARE_LOCAL: (auid: number) => `${BASE_URL}/api/share/local/${auid}`, - - /** - * Get list of local folders configuration. - * @regex ^/api/config/local-folders$ - * @returns {LocalFolderConfig[]} List of local folders configuration - */ - CONFIG_LOCAL_FOLDERS: () => `${BASE_URL}/api/config/local-folders`, -}; - -/** NativeX synchronous API. */ -export type NativeX = { - /** - * Check if the native interface is available. - * @returns Should always return true. - */ - isNative: () => boolean; - - /** - * Set the theme color of the app. - * @param color Color to set - * @param isDark Whether the theme is dark (for navigation bar) - */ - setThemeColor: (color: string, isDark: boolean) => void; - - /** - * Play a tap sound for UI interaction. - */ - playTouchSound: () => void; - - /** - * Make a native toast to the user. - * @param message Message to show - * @param long Whether the toast should be shown for a long time - */ - toast: (message: string, long?: boolean) => void; - - /** - * Start downloading a file from a given URL. - * @param url URL to download from - * @param filename Filename to save as - * @details An error must be shown to the user natively if the download fails. - */ - downloadFromUrl: (url: string, filename: string) => void; - - /** - * Play a video from the given AUID or URL(s). - * @param auid AUID of file (will play local if available) - * @param fileid File ID of the video (only used for file tracking) - * @param urlArray JSON-encoded array of URLs to play - * @details The URL array may contain multiple URLs, e.g. direct playback - * and HLS separately. The native client must try to play the first URL. - */ - playVideo: (auid: string, fileid: string, urlArray: string) => void; - /** - * Destroy the video player. - * @param fileid File ID of the video - * @details The native client must destroy the video player and free up resources. - * If the fileid doesn't match the playing video, the call must be ignored. - */ - destroyVideo: (fileid: string) => void; - - /** - * Set the local folders configuration to show in the timeline. - * @param json JSON-encoded array of LocalFolderConfig - */ - configSetLocalFolders: (json: string) => void; - - /** - * Start the login process - * @param baseUrl Base URL of the Nextcloud instance - * @param loginFlowUrl URL to start the login flow - */ - login: (baseUrl: string, loginFlowUrl: string) => void; - - /** - * Log out from Nextcloud and delete the tokens. - */ - logout: () => void; - - /** - * Reload the app. - */ - reload: () => void; -}; - -/** Setting of whether a local folder is enabled */ -export type LocalFolderConfig = { - id: string; - name: string; - enabled: boolean; -}; - -/** The native interface is a global object that is injected by the native app. */ -const nativex: NativeX = globalThis.nativex; - -/** - * @returns Whether the native interface is available. - */ -export function has() { - return !!nativex; -} - -/** - * Change the theme color of the app to default. - */ -export async function setTheme(color?: string, dark?: boolean) { - if (!has()) return; - - color ??= getComputedStyle(document.body).getPropertyValue('--color-main-background'); - dark ??= - (document.body.hasAttribute('data-theme-default') && window.matchMedia('(prefers-color-scheme: dark)').matches) || - document.body.hasAttribute('data-theme-dark') || - document.body.hasAttribute('data-theme-dark-highcontrast'); - nativex?.setThemeColor?.(color, dark); -} - -/** - * Download a file from the given URL. - */ -export async function downloadFromUrl(url: string) { - // Make HEAD request to get filename - const res = await axios.head(url); - let filename = res.headers['content-disposition']; - if (res.status !== 200 || !filename) return; - - // Extract filename from header without quotes - filename = filename.split('filename="')[1].slice(0, -1); - - // Hand off to download manager - nativex?.downloadFromUrl?.(addOrigin(url), filename); -} - -/** - * Play touch sound. - */ -export async function playTouchSound() { - nativex?.playTouchSound?.(); -} - -/** - * Play a video from the given URL. - * @param photo Photo to play - * @param urls URLs to play (remote) - */ -export async function playVideo(photo: IPhoto, urls: string[]) { - const auid = photo.auid ?? photo.fileid; - nativex?.playVideo?.(auid.toString(), photo.fileid.toString(), JSON.stringify(urls.map(addOrigin))); -} - -/** - * Destroy the video player. - */ -export async function destroyVideo(photo: IPhoto) { - nativex?.destroyVideo?.(photo.fileid.toString()); -} - -/** - * Share a URL with native page. - */ -export async function shareUrl(url: string) { - await axios.get(API.SHARE_URL(addOrigin(url))); -} - -/** - * Download a blob from the given URL and share it. - */ -export async function shareBlobFromUrl(url: string) { - if (url.startsWith(BASE_URL)) { - throw new Error('Cannot share localhost URL'); - } - await axios.get(API.SHARE_BLOB(addOrigin(url))); -} - -/** - * Share a local file with native page. - */ -export async function shareLocal(photo: IPhoto) { - if (!photo.auid) throw new Error('Cannot share local file without AUID'); - await axios.get(API.SHARE_LOCAL(photo.auid)); -} - -/** - * Extend a list of days with local days. - * Fetches the local days from the native interface. - */ -export async function extendDaysWithLocal(days: IDay[]) { - if (!has()) return; - - // Query native part - const res = await fetch(API.DAYS()); - if (!res.ok) return; - const local: IDay[] = await res.json(); - const remoteMap = new Map(days.map((d) => [d.dayid, d])); - - // Merge local days into remote days - for (const day of local) { - const remote = remoteMap.get(day.dayid); - if (remote) { - remote.count = Math.max(remote.count, day.count); - } else { - days.push(day); - } - } - - // TODO: sort depends on view - // (but we show it for only timeline anyway for now) - days.sort((a, b) => b.dayid - a.dayid); -} - -/** - * Extend a list of photos with local photos. - * Fetches the local photos from the native interface and filters out duplicates. - * - * @param dayId Day ID to append local photos to - * @param photos List of photos to append to (duplicates will not be added) - * @returns - */ -export async function extendDayWithLocal(dayId: number, photos: IPhoto[]) { - if (!has()) return; - - // Query native part - const res = await fetch(API.DAY(dayId)); - if (!res.ok) return; - - // Merge local photos into remote photos - const localPhotos: IPhoto[] = await res.json(); - const serverAUIDs = new Set(photos.map((p) => p.auid)); - - // Filter out files that are only available locally - const localOnly = localPhotos.filter((p) => !serverAUIDs.has(p.auid)); - localOnly.forEach((p) => (p.islocal = true)); - photos.push(...localOnly); - - // Sort by epoch value - photos.sort((a, b) => (b.epoch ?? 0) - (a.epoch ?? 0)); -} - -/** - * Request deletion of local photos wherever available. - * @param photos List of photos to delete - * @returns The number of photos for which confirmation was received - * @throws If the request fails - */ -export async function deleteLocalPhotos(photos: IPhoto[], dry: boolean = false): Promise { - if (!has()) return 0; - - const auids = photos.map((p) => p.auid).filter((a) => !!a) as number[]; - const res = await axios.get(SAPI.Q(API.IMAGE_DELETE(auids), { dry })); - return res.data.confirms ? res.data.count : 0; -} - -/** - * Get list of local folders configuration. - * Should be called only if NativeX is available. - */ -export async function getLocalFolders() { - return (await axios.get(API.CONFIG_LOCAL_FOLDERS())).data; -} - -/** - * Set list of local folders configuration. - */ -export async function setLocalFolders(config: LocalFolderConfig[]) { - nativex?.configSetLocalFolders(JSON.stringify(config)); -} - -/** - * Log out from Nextcloud and pass ahead. - */ -export async function logout() { - await axios.get(generateUrl('logout')); - if (!has()) window.location.reload(); - nativex?.logout(); -} - -/** - * Add current origin to URL if doesn't have any protocol or origin. - */ -function addOrigin(url: string) { - return url.match(/^(https?:)?\/\//) - ? url - : url.startsWith('/') - ? `${location.origin}${url}` - : `${location.origin}/${url}`; -} diff --git a/src/native/api.ts b/src/native/api.ts new file mode 100644 index 00000000..144d6909 --- /dev/null +++ b/src/native/api.ts @@ -0,0 +1,165 @@ +const euc = encodeURIComponent; + +/** Access NativeX over localhost */ +export const BASE_URL = 'http://127.0.0.1'; + +/** NativeX asynchronous API */ +export const NAPI = { + /** + * Local days API. + * @regex ^/api/days$ + * @returns {IDay[]} for all locally available days. + */ + DAYS: () => `${BASE_URL}/api/days`, + /** + * Local photos API. + * @regex ^/api/days/\d+$ + * @param dayId Day ID to fetch photos for + * @returns {IPhoto[]} for all locally available photos for this day. + */ + DAY: (dayId: number) => `${BASE_URL}/api/days/${dayId}`, + + /** + * Local photo metadata API. + * @regex ^/api/image/info/\d+$ + * @param fileId File ID of the photo + * @returns {IImageInfo} for the given file ID (local). + */ + IMAGE_INFO: (fileId: number) => `${BASE_URL}/api/image/info/${fileId}`, + + /** + * Delete files using local fileids. + * @regex ^/api/image/delete/\d+(,\d+)*$ + * @param fileIds List of AUIDs to delete + * @param dry (Query) Only check for confirmation and count of local files + * @returns {void} + * @throws Return an error code if the user denies the deletion. + */ + IMAGE_DELETE: (auids: number[]) => `${BASE_URL}/api/image/delete/${auids.join(',')}`, + + /** + * Local photo preview API. + * @regex ^/image/preview/\d+$ + * @param fileId File ID of the photo + * @returns {Blob} JPEG preview of the photo. + */ + IMAGE_PREVIEW: (fileId: number) => `${BASE_URL}/image/preview/${fileId}`, + /** + * Local photo full API. + * @regex ^/image/full/\d+$ + * @param auid AUID of the photo + * @returns {Blob} JPEG full image of the photo. + */ + IMAGE_FULL: (auid: number) => `${BASE_URL}/image/full/${auid}`, + + /** + * Share a URL with native page. + * The native client MUST NOT download the object but share the URL directly. + * @regex ^/api/share/url/.+$ + * @param url URL to share (double-encoded) + * @returns {void} + */ + SHARE_URL: (url: string) => `${BASE_URL}/api/share/url/${euc(euc(url))}`, + /** + * Share an object (as blob) natively using a given URL. + * The native client MUST download the object using a download manager + * and immediately prompt the user to download it. The asynchronous call + * must return only after the object has been downloaded. + * @regex ^/api/share/blob/.+$ + * @param url URL to share (double-encoded) + * @returns {void} + */ + SHARE_BLOB: (url: string) => `${BASE_URL}/api/share/blob/${euc(euc(url))}`, + /** + * Share a local file (as blob) with native page. + * @regex ^/api/share/local/\d+$ + * @param auid AUID of the photo + * @returns {void} + */ + SHARE_LOCAL: (auid: number) => `${BASE_URL}/api/share/local/${auid}`, +}; + +/** NativeX synchronous API. */ +export type NativeX = { + /** + * Check if the native interface is available. + * @returns Should always return true. + */ + isNative: () => boolean; + + /** + * Set the theme color of the app. + * @param color Color to set + * @param isDark Whether the theme is dark (for navigation bar) + */ + setThemeColor: (color: string, isDark: boolean) => void; + + /** + * Play a tap sound for UI interaction. + */ + playTouchSound: () => void; + + /** + * Make a native toast to the user. + * @param message Message to show + * @param long Whether the toast should be shown for a long time + */ + toast: (message: string, long?: boolean) => void; + + /** + * Start the login process + * @param baseUrl Base URL of the Nextcloud instance + * @param loginFlowUrl URL to start the login flow + */ + login: (baseUrl: string, loginFlowUrl: string) => void; + + /** + * Log out from Nextcloud and delete the tokens. + */ + logout: () => void; + + /** + * Reload the app. + */ + reload: () => void; + + /** + * Start downloading a file from a given URL. + * @param url URL to download from + * @param filename Filename to save as + * @details An error must be shown to the user natively if the download fails. + */ + downloadFromUrl: (url: string, filename: string) => void; + + /** + * Play a video from the given AUID or URL(s). + * @param auid AUID of file (will play local if available) + * @param fileid File ID of the video (only used for file tracking) + * @param urlArray JSON-encoded array of URLs to play + * @details The URL array may contain multiple URLs, e.g. direct playback + * and HLS separately. The native client must try to play the first URL. + */ + playVideo: (auid: string, fileid: string, urlArray: string) => void; + /** + * Destroy the video player. + * @param fileid File ID of the video + * @details The native client must destroy the video player and free up resources. + * If the fileid doesn't match the playing video, the call must be ignored. + */ + destroyVideo: (fileid: string) => void; + + /** + * Set the local folders configuration to show in the timeline. + * @param json JSON-encoded array of LocalFolderConfig + */ + configSetLocalFolders: (json: string) => void; + + /** + * Get the local folders configuration to show in the timeline. + * @returns JSON-encoded array of LocalFolderConfig + */ + configGetLocalFolders: () => string; +}; + +/** The native interface is a global object that is injected by the native app. */ +export const nativex: NativeX = globalThis.nativex; diff --git a/src/native/basic.ts b/src/native/basic.ts new file mode 100644 index 00000000..1bcc3d32 --- /dev/null +++ b/src/native/basic.ts @@ -0,0 +1,51 @@ +import axios from '@nextcloud/axios'; +import { generateUrl } from '@nextcloud/router'; +import { nativex } from './api'; + +/** + * @returns Whether the native interface is available. + */ +export function has() { + return !!nativex; +} + +/** + * Change the theme color of the app to default. + */ +export async function setTheme(color?: string, dark?: boolean) { + if (!has()) return; + + color ??= getComputedStyle(document.body).getPropertyValue('--color-main-background'); + dark ??= + (document.body.hasAttribute('data-theme-default') && window.matchMedia('(prefers-color-scheme: dark)').matches) || + document.body.hasAttribute('data-theme-dark') || + document.body.hasAttribute('data-theme-dark-highcontrast'); + nativex?.setThemeColor?.(color, dark); +} + +/** + * Play touch sound. + */ +export async function playTouchSound() { + nativex?.playTouchSound?.(); +} + +/** + * Log out from Nextcloud and pass ahead. + */ +export async function logout() { + await axios.get(generateUrl('logout')); + if (!has()) window.location.reload(); + nativex?.logout(); +} + +/** + * Add current origin to URL if doesn't have any protocol or origin. + */ +export function addOrigin(url: string) { + return url.match(/^(https?:)?\/\//) + ? url + : url.startsWith('/') + ? `${location.origin}${url}` + : `${location.origin}/${url}`; +} diff --git a/src/native/config.ts b/src/native/config.ts new file mode 100644 index 00000000..fbbd6247 --- /dev/null +++ b/src/native/config.ts @@ -0,0 +1,23 @@ +import { nativex } from './api'; + +/** Setting of whether a local folder is enabled */ +export type LocalFolderConfig = { + id: string; + name: string; + enabled: boolean; +}; + +/** + * Set list of local folders configuration. + */ +export function setLocalFolders(config: LocalFolderConfig[]) { + return nativex?.configSetLocalFolders(JSON.stringify(config)); +} + +/** + * Get list of local folders configuration. + * Should be called only if NativeX is available. + */ +export function getLocalFolders() { + return JSON.parse(nativex?.configGetLocalFolders?.() ?? '[]') as LocalFolderConfig[]; +} diff --git a/src/native/days.ts b/src/native/days.ts new file mode 100644 index 00000000..e82108df --- /dev/null +++ b/src/native/days.ts @@ -0,0 +1,75 @@ +import axios from '@nextcloud/axios'; +import { NAPI } from './api'; +import { API } from '../services/API'; +import { has } from './basic'; +import type { IDay, IPhoto } from '../types'; + +/** + * Extend a list of days with local days. + * Fetches the local days from the native interface. + */ +export async function extendDaysWithLocal(days: IDay[]) { + if (!has()) return; + + // Query native part + const res = await fetch(NAPI.DAYS()); + if (!res.ok) return; + const local: IDay[] = await res.json(); + const remoteMap = new Map(days.map((d) => [d.dayid, d])); + + // Merge local days into remote days + for (const day of local) { + const remote = remoteMap.get(day.dayid); + if (remote) { + remote.count = Math.max(remote.count, day.count); + } else { + days.push(day); + } + } + + // TODO: sort depends on view + // (but we show it for only timeline anyway for now) + days.sort((a, b) => b.dayid - a.dayid); +} + +/** + * Extend a list of photos with local photos. + * Fetches the local photos from the native interface and filters out duplicates. + * + * @param dayId Day ID to append local photos to + * @param photos List of photos to append to (duplicates will not be added) + * @returns + */ +export async function extendDayWithLocal(dayId: number, photos: IPhoto[]) { + if (!has()) return; + + // Query native part + const res = await fetch(NAPI.DAY(dayId)); + if (!res.ok) return; + + // Merge local photos into remote photos + const localPhotos: IPhoto[] = await res.json(); + const serverAUIDs = new Set(photos.map((p) => p.auid)); + + // Filter out files that are only available locally + const localOnly = localPhotos.filter((p) => !serverAUIDs.has(p.auid)); + localOnly.forEach((p) => (p.islocal = true)); + photos.push(...localOnly); + + // Sort by epoch value + photos.sort((a, b) => (b.epoch ?? 0) - (a.epoch ?? 0)); +} + +/** + * Request deletion of local photos wherever available. + * @param photos List of photos to delete + * @returns The number of photos for which confirmation was received + * @throws If the request fails + */ +export async function deleteLocalPhotos(photos: IPhoto[], dry: boolean = false): Promise { + if (!has()) return 0; + + const auids = photos.map((p) => p.auid).filter((a) => !!a) as number[]; + const res = await axios.get(API.Q(NAPI.IMAGE_DELETE(auids), { dry })); + return res.data.confirms ? res.data.count : 0; +} diff --git a/src/native/index.ts b/src/native/index.ts new file mode 100644 index 00000000..884163e0 --- /dev/null +++ b/src/native/index.ts @@ -0,0 +1,6 @@ +export * from './api'; +export * from './basic'; +export * from './config'; +export * from './days'; +export * from './share'; +export * from './video'; diff --git a/src/native/share.ts b/src/native/share.ts new file mode 100644 index 00000000..a51f71bf --- /dev/null +++ b/src/native/share.ts @@ -0,0 +1,45 @@ +import axios from '@nextcloud/axios'; +import { BASE_URL, NAPI, nativex } from './api'; +import { addOrigin } from './basic'; +import type { IPhoto } from '../types'; + +/** + * Download a file from the given URL. + */ +export async function downloadFromUrl(url: string) { + // Make HEAD request to get filename + const res = await axios.head(url); + let filename = res.headers['content-disposition']; + if (res.status !== 200 || !filename) return; + + // Extract filename from header without quotes + filename = filename.split('filename="')[1].slice(0, -1); + + // Hand off to download manager + nativex?.downloadFromUrl?.(addOrigin(url), filename); +} + +/** + * Share a URL with native page. + */ +export async function shareUrl(url: string) { + await axios.get(NAPI.SHARE_URL(addOrigin(url))); +} + +/** + * Download a blob from the given URL and share it. + */ +export async function shareBlobFromUrl(url: string) { + if (url.startsWith(BASE_URL)) { + throw new Error('Cannot share localhost URL'); + } + await axios.get(NAPI.SHARE_BLOB(addOrigin(url))); +} + +/** + * Share a local file with native page. + */ +export async function shareLocal(photo: IPhoto) { + if (!photo.auid) throw new Error('Cannot share local file without AUID'); + await axios.get(NAPI.SHARE_LOCAL(photo.auid)); +} diff --git a/src/native/video.ts b/src/native/video.ts new file mode 100644 index 00000000..fa8b0e51 --- /dev/null +++ b/src/native/video.ts @@ -0,0 +1,20 @@ +import { nativex } from './api'; +import { addOrigin } from './basic'; +import type { IPhoto } from '../types'; + +/** + * Play a video from the given URL. + * @param photo Photo to play + * @param urls URLs to play (remote) + */ +export async function playVideo(photo: IPhoto, urls: string[]) { + const auid = photo.auid ?? photo.fileid; + nativex?.playVideo?.(auid.toString(), photo.fileid.toString(), JSON.stringify(urls.map(addOrigin))); +} + +/** + * Destroy the video player. + */ +export async function destroyVideo(photo: IPhoto) { + nativex?.destroyVideo?.(photo.fileid.toString()); +} diff --git a/src/services/utils/helpers.ts b/src/services/utils/helpers.ts index 0ca01811..2d70e4e0 100644 --- a/src/services/utils/helpers.ts +++ b/src/services/utils/helpers.ts @@ -66,7 +66,7 @@ export function getPreviewUrl(opts: PreviewOptsSize | PreviewOptsMsize | Preview // Native preview if (isLocalPhoto(photo)) { - return API.Q(nativex.API.IMAGE_PREVIEW(photo.fileid), { c: photo.etag }); + return API.Q(nativex.NAPI.IMAGE_PREVIEW(photo.fileid), { c: photo.etag }); } // Screen-appropriate size @@ -114,7 +114,7 @@ export function getImageInfoUrl(photo: IPhoto | number): string { const fileid = typeof photo === 'number' ? photo : photo.fileid; if (typeof photo === 'object' && isLocalPhoto(photo)) { - return nativex.API.IMAGE_INFO(fileid); + return nativex.NAPI.IMAGE_INFO(fileid); } return API.IMAGE_INFO(fileid);