feat: expo-audioによる音声再生機能を追加
- expo-audio依存を追加、app.jsonにプラグイン設定 - 設定画面ヘッダー画像タップで音声再生 - expo-assetでOTA対応のアセット解決 + file:// prefix除去(SDK52バグ回避) - 音声ファイルはmp3のみ保持(wav/m4a/ogg削除) - SDK53以降でinterruptionMode: duckOthers対応予定
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"buildNumber": "52",
|
||||
"buildNumber": "53",
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "jrshikokuinfo.xprocess.hrkn",
|
||||
"config": {
|
||||
@@ -46,6 +46,34 @@
|
||||
"android": {
|
||||
"package": "jrshikokuinfo.xprocess.hrkn",
|
||||
"versionCode": 29,
|
||||
"intentFilters": [
|
||||
{
|
||||
"action": "VIEW",
|
||||
"data": [
|
||||
{
|
||||
"scheme": "jrshikoku"
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"BROWSABLE",
|
||||
"DEFAULT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "VIEW",
|
||||
"data": [
|
||||
{
|
||||
"scheme": "jrshikoku",
|
||||
"host": "open",
|
||||
"pathPrefix": "/felica"
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"BROWSABLE",
|
||||
"DEFAULT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"ACCESS_FINE_LOCATION",
|
||||
"NFC",
|
||||
@@ -461,7 +489,8 @@
|
||||
"kotlinVersion": "1.9.25"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"expo-audio"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -17,8 +17,15 @@ import { SwitchArea } from "../atom/SwitchArea";
|
||||
import { useNotification } from "../../stateBox/useNotifications";
|
||||
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
|
||||
import { useTrainMenu } from "../../stateBox/useTrainMenu";
|
||||
import { Asset } from "expo-asset";
|
||||
import {
|
||||
useAudioPlayer,
|
||||
setAudioModeAsync,
|
||||
} from "expo-audio";
|
||||
import type { AudioSource } from "expo-audio";
|
||||
|
||||
const versionCode = "6.2.1.1"; // Update this version code as needed
|
||||
const settingsPreviewSound = require("../../assets/sound/rikka-test.mp3");
|
||||
|
||||
export const SettingTopPage = ({
|
||||
testNFC,
|
||||
@@ -30,6 +37,62 @@ export const SettingTopPage = ({
|
||||
const { expoPushToken } = useNotification();
|
||||
const { updatePermission, dataSourcePermission } = useTrainMenu();
|
||||
const navigation = useNavigation();
|
||||
|
||||
// expo-asset でローカルパスを取得し、expo-audio に渡す
|
||||
// (SDK 52 の expo-audio は file:// URI を正しく処理できないため prefix を除去)
|
||||
const [resolvedSource, setResolvedSource] = useState<AudioSource>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
const resolve = async () => {
|
||||
try {
|
||||
const asset = Asset.fromModule(settingsPreviewSound);
|
||||
await asset.downloadAsync();
|
||||
const localUri = asset.localUri;
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// file:// を剥がしてパスだけにする
|
||||
// (expo-audio ネイティブ AudioModule.kt の File(uri) バグ回避)
|
||||
const strippedPath = localUri
|
||||
? localUri.replace(/^file:\/\//, "")
|
||||
: null;
|
||||
|
||||
if (strippedPath) {
|
||||
setResolvedSource({ uri: strippedPath });
|
||||
}
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
console.warn("Failed to resolve audio asset", error);
|
||||
}
|
||||
};
|
||||
|
||||
resolve();
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const previewPlayer = useAudioPlayer(resolvedSource);
|
||||
|
||||
const onPressHeaderImage = useCallback(async () => {
|
||||
try {
|
||||
if (Platform.OS === "ios") {
|
||||
await setAudioModeAsync({
|
||||
playsInSilentMode: false,
|
||||
shouldPlayInBackground: false,
|
||||
interruptionMode: "mixWithOthers",
|
||||
});
|
||||
}
|
||||
if (previewPlayer.playing) previewPlayer.pause();
|
||||
previewPlayer.volume = 1;
|
||||
await previewPlayer.seekTo(0);
|
||||
previewPlayer.play();
|
||||
} catch (error) {
|
||||
console.warn("Failed to play preview sound", error);
|
||||
}
|
||||
}, [previewPlayer]);
|
||||
|
||||
// admin またはいずれかのソース権限を持つ場合のみ表示
|
||||
const canAccessDataSourceSettings =
|
||||
updatePermission || Object.values(dataSourcePermission).some(Boolean);
|
||||
@@ -42,15 +105,17 @@ export const SettingTopPage = ({
|
||||
<ScrollView style={{ flex: 1, backgroundColor: "#f8f8fc" }}>
|
||||
<View style={{ height: 300, padding: 10 }}>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Image
|
||||
source={require("../../assets/Header.png")}
|
||||
style={{
|
||||
aspectRatio: 8.08,
|
||||
height: undefined,
|
||||
width: width - 20,
|
||||
borderRadius: 5,
|
||||
}}
|
||||
/>
|
||||
<TouchableOpacity activeOpacity={0.9} onPress={onPressHeaderImage}>
|
||||
<Image
|
||||
source={require("../../assets/Header.png")}
|
||||
style={{
|
||||
aspectRatio: 8.08,
|
||||
height: undefined,
|
||||
width: width - 20,
|
||||
borderRadius: 5,
|
||||
}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={{ flexDirection: "row", paddingTop: 10 }}>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text>内部バージョン: {versionCode}</Text>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"dayjs": "^1.11.9",
|
||||
"expo": "^52.0.0",
|
||||
"expo-alternate-app-icons": "^1.3.0",
|
||||
"expo-audio": "~0.3.5",
|
||||
"expo-build-properties": "~0.13.1",
|
||||
"expo-clipboard": "~7.0.1",
|
||||
"expo-constants": "~17.0.4",
|
||||
|
||||
Reference in New Issue
Block a user