mirror of
https://github.com/jellyfin/jellyfin-expo.git
synced 2024-11-23 05:59:39 +00:00
Merge pull request #408 from thornbill/transcode-cleanup
Report to server when download stops
This commit is contained in:
commit
94e33b69bc
21
App.js
21
App.js
@ -8,6 +8,7 @@
|
||||
import 'react-native-url-polyfill/auto';
|
||||
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api/playstate-api';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { Asset } from 'expo-asset';
|
||||
@ -20,7 +21,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { AsyncTrunk } from 'mobx-sync-lite';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useColorScheme } from 'react-native';
|
||||
import { Alert, useColorScheme } from 'react-native';
|
||||
import { ThemeContext, ThemeProvider } from 'react-native-elements';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
|
||||
@ -133,6 +134,7 @@ const App = observer(({ skipLoadingScreen }) => {
|
||||
|
||||
// TODO: The resumable should be saved to allow pausing/resuming downloads
|
||||
|
||||
// Download the file
|
||||
try {
|
||||
download.isDownloading = true;
|
||||
await resumable.downloadAsync();
|
||||
@ -140,8 +142,25 @@ const App = observer(({ skipLoadingScreen }) => {
|
||||
download.isDownloading = false;
|
||||
} catch (e) {
|
||||
console.error('[App] Download failed', e);
|
||||
Alert.alert('Download Failed', `"${download.title}" failed to download.`);
|
||||
|
||||
// TODO: If a download fails, we should probably remove it from the queue
|
||||
download.isDownloading = false;
|
||||
}
|
||||
|
||||
// Report download has stopped
|
||||
const serverUrl = download.serverUrl.endsWith('/') ? download.serverUrl.slice(0, -1) : download.serverUrl;
|
||||
const api = rootStore.sdk.createApi(serverUrl, download.apiKey);
|
||||
console.log('[App] Reporting download stopped', download.sessionId);
|
||||
getPlaystateApi(api)
|
||||
.reportPlaybackStopped({
|
||||
playbackStopInfo: {
|
||||
PlaySessionId: download.sessionId
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('[App] Failed reporting download stopped', err.response || err.request || err.message);
|
||||
});
|
||||
};
|
||||
|
||||
rootStore.downloadStore.downloads
|
||||
|
@ -47,3 +47,11 @@ jest.mock('@react-navigation/native/lib/commonjs/useLinking.native', () => ({
|
||||
/* Safe Area Context Mocks */
|
||||
import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock';
|
||||
jest.mock('react-native-safe-area-context', () => mockSafeAreaContext);
|
||||
|
||||
/* UUID Mocks */
|
||||
jest.mock('uuid', () => {
|
||||
let value = 0;
|
||||
return {
|
||||
v4: () => `uuid-${value++}`
|
||||
};
|
||||
});
|
||||
|
@ -7,6 +7,7 @@
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import { computed, decorate, observable } from 'mobx';
|
||||
import { ignore } from 'mobx-sync-lite';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export default class DownloadModel {
|
||||
isComplete = false
|
||||
@ -15,6 +16,8 @@ export default class DownloadModel {
|
||||
|
||||
apiKey: string
|
||||
itemId: string
|
||||
/** The "play" session ID for reporting a download has stopped. */
|
||||
sessionId = uuidv4()
|
||||
serverId: string
|
||||
serverUrl: string
|
||||
|
||||
@ -61,6 +64,7 @@ export default class DownloadModel {
|
||||
const streamParams = new URLSearchParams({
|
||||
deviceId,
|
||||
api_key: this.apiKey,
|
||||
playSessionId: this.sessionId,
|
||||
// TODO: add mediaSourceId to support alternate media versions
|
||||
videoCodec: 'hevc,h264',
|
||||
audioCodec: 'aac,mp3,ac3,eac3,flac,alac',
|
||||
@ -79,6 +83,7 @@ decorate(DownloadModel, {
|
||||
isNew: observable,
|
||||
apiKey: observable,
|
||||
itemId: observable,
|
||||
sessionId: observable,
|
||||
serverId: observable,
|
||||
serverUrl: observable,
|
||||
title: observable,
|
||||
|
@ -26,6 +26,7 @@ describe('DownloadModel', () => {
|
||||
|
||||
expect(download.apiKey).toBe('api-key');
|
||||
expect(download.itemId).toBe('item-id');
|
||||
expect(download.sessionId).toBe('uuid-0');
|
||||
expect(download.serverId).toBe('server-id');
|
||||
expect(download.serverUrl).toBe('https://example.com/');
|
||||
|
||||
@ -42,6 +43,6 @@ describe('DownloadModel', () => {
|
||||
expect(download.localFilename).toBe('file name.mp4');
|
||||
expect(download.localPath).toBe(`${DOCUMENT_DIRECTORY}server-id/item-id/`);
|
||||
expect(download.uri).toBe(`${DOCUMENT_DIRECTORY}server-id/item-id/file%20name.mp4`);
|
||||
expect(download.getStreamUrl('device-id').toString()).toBe('https://example.com/Videos/item-id/stream.mp4?deviceId=device-id&api_key=api-key&videoCodec=hevc%2Ch264&audioCodec=aac%2Cmp3%2Cac3%2Ceac3%2Cflac%2Calac&maxAudioChannels=6');
|
||||
expect(download.getStreamUrl('device-id').toString()).toBe('https://example.com/Videos/item-id/stream.mp4?deviceId=device-id&api_key=api-key&playSessionId=uuid-0&videoCodec=hevc%2Ch264&audioCodec=aac%2Cmp3%2Cac3%2Ceac3%2Cflac%2Calac&maxAudioChannels=6');
|
||||
});
|
||||
});
|
||||
|
44
package-lock.json
generated
44
package-lock.json
generated
@ -2126,6 +2126,41 @@
|
||||
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
||||
"dev": true
|
||||
},
|
||||
"@jellyfin/sdk": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.7.0.tgz",
|
||||
"integrity": "sha512-GNoGv+2qY+xK7WpO7sUUNpZvzgN7RwXMyOhIy9mE/LdDSr6bqZHwrzT1Pv0+vUW7Epw67bwIMWuYivyBYejEHw==",
|
||||
"requires": {
|
||||
"axios": "0.27.2",
|
||||
"compare-versions": "5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"compare-versions": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz",
|
||||
"integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jest/console": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz",
|
||||
@ -3694,6 +3729,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/webpack": {
|
||||
"version": "4.41.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz",
|
||||
@ -9320,8 +9361,7 @@
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"fontfaceobserver": {
|
||||
"version": "2.3.0",
|
||||
|
@ -34,6 +34,7 @@
|
||||
"Android >= 5"
|
||||
],
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.7.0",
|
||||
"@react-native-async-storage/async-storage": "~1.17.3",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-navigation/bottom-tabs": "^6.0.7",
|
||||
@ -82,6 +83,7 @@
|
||||
"@testing-library/react-native": "^7.2.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"babel-preset-expo": "~9.1.0",
|
||||
|
@ -33,6 +33,7 @@ exports[`DownloadScreen should render correctly 1`] = `
|
||||
"itemId": "item-id",
|
||||
"serverId": "server-id",
|
||||
"serverUrl": "https://example.com/",
|
||||
"sessionId": "uuid-0",
|
||||
"title": "title",
|
||||
},
|
||||
Object {
|
||||
@ -45,6 +46,7 @@ exports[`DownloadScreen should render correctly 1`] = `
|
||||
"itemId": "item-id-2",
|
||||
"serverId": "server-id",
|
||||
"serverUrl": "https://test2.example.com/",
|
||||
"sessionId": "uuid-1",
|
||||
"title": "other title",
|
||||
},
|
||||
]
|
||||
|
@ -7,10 +7,14 @@
|
||||
// polyfill crypto.getRandomValues
|
||||
import 'react-native-get-random-values';
|
||||
|
||||
import { action, decorate, observable } from 'mobx';
|
||||
import { Jellyfin } from '@jellyfin/sdk';
|
||||
import Constants from 'expo-constants';
|
||||
import { action, computed, decorate, observable } from 'mobx';
|
||||
import { ignore } from 'mobx-sync-lite';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getAppName, getSafeDeviceName } from '../utils/Device';
|
||||
|
||||
import DownloadStore from './DownloadStore';
|
||||
import MediaStore from './MediaStore';
|
||||
import ServerStore from './ServerStore';
|
||||
@ -47,6 +51,19 @@ export default class RootStore {
|
||||
serverStore = new ServerStore()
|
||||
settingStore = new SettingStore()
|
||||
|
||||
get sdk() {
|
||||
return new Jellyfin({
|
||||
clientInfo: {
|
||||
name: getAppName(),
|
||||
version: Constants.nativeAppVersion
|
||||
},
|
||||
deviceInfo: {
|
||||
name: getSafeDeviceName(),
|
||||
id: this.deviceId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deviceId = uuidv4();
|
||||
|
||||
@ -69,5 +86,6 @@ decorate(RootStore, {
|
||||
isFullscreen: [ ignore, observable ],
|
||||
isReloadRequired: [ ignore, observable ],
|
||||
didPlayerCloseManually: [ ignore, observable ],
|
||||
sdk: computed,
|
||||
reset: action
|
||||
});
|
||||
|
@ -3,6 +3,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Jellyfin } from '@jellyfin/sdk';
|
||||
|
||||
import DownloadStore from '../DownloadStore';
|
||||
import MediaStore from '../MediaStore';
|
||||
import RootStore from '../RootStore';
|
||||
@ -17,6 +19,10 @@ describe('RootStore', () => {
|
||||
expect(store.isFullscreen).toBe(false);
|
||||
expect(store.isReloadRequired).toBe(false);
|
||||
expect(store.didPlayerCloseManually).toBe(true);
|
||||
|
||||
expect(store.sdk).toBeInstanceOf(Jellyfin);
|
||||
expect(store.sdk.deviceInfo.id).toBe(store.deviceId);
|
||||
|
||||
expect(store.downloadStore).toBeInstanceOf(DownloadStore);
|
||||
expect(store.mediaStore).toBeInstanceOf(MediaStore);
|
||||
expect(store.serverStore).toBeInstanceOf(ServerStore);
|
||||
|
Loading…
Reference in New Issue
Block a user