Просмотр исходного кода

音乐播放支持离开设备页面后继续播放

liyuliangjiazai 2 месяцев назад
Родитель
Сommit
0d6b450328

+ 2 - 1
code/ajyApp/pages/device/deviceInfo/deviceInfo.nvue

@@ -408,7 +408,8 @@ export default {
 		}
 	},
 	onUnload() {
-		this.destroyAudio()
+		// 离开页面时解绑音频事件(音频继续播放,不销毁)
+		this.detachAudio()
 		// 离开页面时不断开蓝牙连接,仅停止扫描
 		// 下次进入相同设备可复用连接
 		this.cleanupOnLeave()

+ 38 - 56
code/ajyApp/pages/device/deviceInfo/mixins/audio-mixin.js

@@ -1,84 +1,66 @@
 /**
  * 音频播放 mixin
- * 负责:白噪音初始化、播放/暂停、切换音源
+ * 代理 audioStore,实现跨页面音频持久化播放
+ * 退出页面后音频继续播放,再次进入页面恢复播放状态
  */
-let innerAudioContext = null
+import { useAudioStore } from '@/stores/audio'
 
 export default {
-	data() {
-		return {
-			audiolist: [
-				{ name: '河流', posterimg: '/static/device/heliu.png', url: '/static/audio/audio1.mp3' },
-				{ name: '下雨', posterimg: '/static/device/heliu.png', url: '/static/audio/audio2.mp3' }
-			],
-			curAudioInx: 0,
-			totalTime: 0,
-			currentTime: 0,
-			ispause: true,
-			isShowDrawer3: false
+	computed: {
+		_audioStore() {
+			return useAudioStore()
+		},
+		audiolist() {
+			return this._audioStore.audiolist
+		},
+		curAudioInx: {
+			get() { return this._audioStore.curAudioInx },
+			set(val) { this._audioStore.curAudioInx = val }
+		},
+		totalTime() {
+			return this._audioStore.totalTime
+		},
+		currentTime() {
+			return this._audioStore.currentTime
+		},
+		ispause() {
+			return this._audioStore.ispause
+		},
+		isShowDrawer3: {
+			get() { return this._audioStore.isShowDrawer3 },
+			set(val) { this._audioStore.isShowDrawer3 = val }
 		}
 	},
 	methods: {
 		initAudio() {
-			innerAudioContext = uni.createInnerAudioContext()
-			innerAudioContext.src = '/static/audio/audio1.mp3'
-			innerAudioContext.loop = true
-			innerAudioContext.onPlay(() => {
-				this.totalTime = innerAudioContext.duration
-			})
-			innerAudioContext.onCanplay(() => {
-				this.totalTime = innerAudioContext.duration
-			})
-			innerAudioContext.onError((res) => {
-				console.log('播放错误', res.errMsg)
-			})
-			innerAudioContext.onTimeUpdate(() => {
-				this.currentTime = innerAudioContext.currentTime
-			})
+			this._audioStore.initAudio()
 		},
 
 		formatTime(totalSeconds) {
-			totalSeconds = Math.floor(totalSeconds)
-			const minutes = Math.floor(totalSeconds / 60)
-			const seconds = totalSeconds % 60
-			return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0')
+			return this._audioStore.formatTime(totalSeconds)
 		},
 
 		changeAudio(url, inx) {
-			this.curAudioInx = inx
-			if (innerAudioContext) {
-				innerAudioContext.stop()
-				setTimeout(() => {
-					innerAudioContext.src = url
-					this.ispause = true
-					this.playAudio()
-				}, 500)
-			}
+			this._audioStore.changeAudio(url, inx)
 		},
 
 		playAudio() {
-			if (!innerAudioContext) return
-			if (this.ispause) {
-				innerAudioContext.play()
-			} else {
-				innerAudioContext.pause()
-			}
-			this.ispause = !this.ispause
+			this._audioStore.playAudio()
 		},
 
 		/** 供 ble-mixin 模式切换时调用 */
 		_stopAudioOnModeChange() {
-			if (innerAudioContext) {
-				innerAudioContext.stop()
-			}
+			this._audioStore.stopAudio()
+		},
+
+		/** 页面离开时解绑事件(音频继续播放) */
+		detachAudio() {
+			this._audioStore.detachEvents()
 		},
 
+		/** 彻底销毁(一般不调用,仅供极端场景) */
 		destroyAudio() {
-			if (innerAudioContext) {
-				innerAudioContext.stop()
-				innerAudioContext.destroy()
-				innerAudioContext = null
-			}
+			this._audioStore.destroyAudio()
 		}
 	}
 }

+ 175 - 0
code/ajyApp/stores/audio.js

@@ -0,0 +1,175 @@
+/**
+ * 音频播放 Pinia Store
+ * 跨页面共享音频播放状态,音频实例存放在 globalData 中确保单例
+ */
+import { defineStore } from 'pinia'
+
+const AUDIO_LIST = [
+	{ name: '河流', posterimg: '/static/device/heliu.png', url: '/static/audio/audio1.mp3' },
+	{ name: '下雨', posterimg: '/static/device/heliu.png', url: '/static/audio/audio2.mp3' }
+]
+
+// 通过 globalData 确保 InnerAudioContext 跨页面单例
+function _getAudioCtx() {
+	const app = getApp()
+	if (!app.globalData) app.globalData = {}
+	return app.globalData._audioContext || null
+}
+
+function _setAudioCtx(ctx) {
+	const app = getApp()
+	if (!app.globalData) app.globalData = {}
+	app.globalData._audioContext = ctx
+}
+
+export const useAudioStore = defineStore('audio', {
+	state: () => ({
+		audiolist: AUDIO_LIST,
+		curAudioInx: 0,
+		totalTime: 0,
+		currentTime: 0,
+		ispause: true, // true=暂停中, false=播放中
+		isShowDrawer3: false
+	}),
+
+	actions: {
+		/** 初始化或恢复音频上下文(页面进入时调用) */
+		initAudio() {
+			let ctx = _getAudioCtx()
+			if (ctx) {
+				// 已有实例,重新绑定事件监听
+				this._bindEvents(ctx)
+				// 同步当前播放状态
+				if (!ctx.paused && ctx.src) {
+					this.ispause = false
+					this.currentTime = ctx.currentTime || 0
+					this.totalTime = ctx.duration || 0
+				}
+				return
+			}
+			// 首次创建
+			ctx = uni.createInnerAudioContext()
+			ctx.src = this.audiolist[this.curAudioInx].url
+			ctx.loop = true
+			_setAudioCtx(ctx)
+			this._bindEvents(ctx)
+		},
+
+		/** 绑定事件监听 */
+		_bindEvents(ctx) {
+			// 先清除旧监听,避免重复绑定(部分平台新建ctx调用off会报错,需保护)
+			try {
+				ctx.offPlay && ctx.offPlay()
+				ctx.offCanplay && ctx.offCanplay()
+				ctx.offError && ctx.offError()
+				ctx.offTimeUpdate && ctx.offTimeUpdate()
+				ctx.offPause && ctx.offPause()
+				ctx.offStop && ctx.offStop()
+			} catch (e) {
+				// 忽略:新创建的ctx可能没有已注册的监听器
+			}
+
+			ctx.onPlay(() => {
+				this.totalTime = ctx.duration || 0
+				this.ispause = false
+			})
+			ctx.onCanplay(() => {
+				this.totalTime = ctx.duration || 0
+			})
+			ctx.onError((res) => {
+				console.log('播放错误', res.errMsg)
+			})
+			ctx.onTimeUpdate(() => {
+				this.currentTime = ctx.currentTime || 0
+			})
+			ctx.onPause(() => {
+				this.ispause = true
+			})
+			ctx.onStop(() => {
+				this.ispause = true
+				this.currentTime = 0
+			})
+		},
+
+		/** 页面离开时解绑事件(不销毁音频) */
+		detachEvents() {
+			const ctx = _getAudioCtx()
+			if (ctx) {
+				try {
+					ctx.offPlay && ctx.offPlay()
+					ctx.offCanplay && ctx.offCanplay()
+					ctx.offError && ctx.offError()
+					ctx.offTimeUpdate && ctx.offTimeUpdate()
+					ctx.offPause && ctx.offPause()
+					ctx.offStop && ctx.offStop()
+				} catch (e) {
+					// 忽略
+				}
+			}
+		},
+
+		/** 切换播放/暂停 */
+		playAudio() {
+			const ctx = _getAudioCtx()
+			if (!ctx) return
+			if (this.ispause) {
+				ctx.play()
+			} else {
+				ctx.pause()
+			}
+		},
+
+		/** 切换音源(切换后主动播放) */
+		changeAudio(url, inx) {
+			this.curAudioInx = inx
+			this.ispause = false
+			const ctx = _getAudioCtx()
+			if (ctx) {
+				ctx.stop()
+				setTimeout(() => {
+					ctx.src = url
+					ctx.play()
+				}, 300)
+			} else {
+				// 音频上下文不存在时,先初始化再播放
+				this.initAudio()
+				const newCtx = _getAudioCtx()
+				if (newCtx) {
+					newCtx.src = url
+					newCtx.play()
+				}
+			}
+		},
+
+		/** 停止播放(模式切换时调用) */
+		stopAudio() {
+			const ctx = _getAudioCtx()
+			if (ctx) {
+				ctx.stop()
+			}
+			this.ispause = true
+			this.currentTime = 0
+		},
+
+		/** 彻底销毁音频(APP退出等极端场景) */
+		destroyAudio() {
+			const ctx = _getAudioCtx()
+			if (ctx) {
+				ctx.stop()
+				ctx.destroy()
+				_setAudioCtx(null)
+			}
+			this.ispause = true
+			this.currentTime = 0
+			this.totalTime = 0
+		},
+
+		/** 格式化时间 */
+		formatTime(totalSeconds) {
+			totalSeconds = Math.floor(totalSeconds || 0)
+			const minutes = Math.floor(totalSeconds / 60)
+			const seconds = totalSeconds % 60
+			return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0')
+		}
+	}
+})