|
@@ -1,8 +1,19 @@
|
|
|
-import { defineComponent, reactive } from 'vue'
|
|
|
-import { ElButton, ElDropdown, ElDropdownMenu, ElDropdownItem, ElSlider, ElDialog, ElIcon } from 'element-plus'
|
|
|
+import { defineComponent, reactive, watch } from 'vue'
|
|
|
+import {
|
|
|
+ ElButton,
|
|
|
+ ElDropdown,
|
|
|
+ ElDropdownMenu,
|
|
|
+ ElDropdownItem,
|
|
|
+ ElSlider,
|
|
|
+ ElDialog,
|
|
|
+ ElIcon,
|
|
|
+} from 'element-plus'
|
|
|
import runtime, * as RuntimeUtils from './runtime'
|
|
|
+import MicroPhoneIcon from './images/microphone.svg?raw'
|
|
|
import styles from './action-bar.module.less'
|
|
|
import Share from './share'
|
|
|
+import { requireMedia } from './helpers'
|
|
|
+import SvgIcon from '../svg-icon'
|
|
|
|
|
|
export const state = reactive({
|
|
|
volume: 0,
|
|
@@ -10,30 +21,128 @@ export const state = reactive({
|
|
|
camera: false, // 摄像头
|
|
|
volume: false, // 声音调节
|
|
|
microphone: false, // 麦克风
|
|
|
+ microphone2: false, // 系统音频
|
|
|
screen: false, // 共享屏幕
|
|
|
share: false, // 分享
|
|
|
},
|
|
|
- shareVisiable: false
|
|
|
+ shareVisiable: false,
|
|
|
+ binded: false,
|
|
|
})
|
|
|
|
|
|
export default defineComponent({
|
|
|
name: 'LiveBroadcast-ActionBar',
|
|
|
computed: {
|
|
|
isCameraDisabled() {
|
|
|
- return state.barStatus.camera && runtime.deviceStatus.camera !== 'denied' && runtime.cameras.length
|
|
|
+ return (
|
|
|
+ state.barStatus.camera &&
|
|
|
+ runtime.deviceStatus.camera !== 'denied' &&
|
|
|
+ runtime.cameras.length
|
|
|
+ )
|
|
|
},
|
|
|
isMicrophoneDisabled() {
|
|
|
- const isDisabled = state.barStatus.microphone && runtime.deviceStatus.microphone !== 'denied' && runtime.microphones.length
|
|
|
+ const isDisabled =
|
|
|
+ state.barStatus.microphone &&
|
|
|
+ runtime.deviceStatus.microphone !== 'denied' &&
|
|
|
+ runtime.microphones.length
|
|
|
return isDisabled
|
|
|
},
|
|
|
+ isMicrophone2Disabled() {
|
|
|
+ console.log(runtime.selectedMicrophone2)
|
|
|
+ const isDisabled =
|
|
|
+ state.barStatus.microphone2 &&
|
|
|
+ runtime.deviceStatus.microphone !== 'denied' &&
|
|
|
+ runtime.microphones.length
|
|
|
+ return isDisabled || !runtime.selectedMicrophone2
|
|
|
+ },
|
|
|
isVolumeDisabled() {
|
|
|
return state.volume === 0
|
|
|
- }
|
|
|
+ },
|
|
|
},
|
|
|
mounted() {
|
|
|
- console.log(runtime.cameras, runtime.cameras.length)
|
|
|
+ RuntimeUtils.runtimeEvent.on('microphoneChange', this.listenMicophoneAudioTrack)
|
|
|
+ // RuntimeUtils.runtimeEvent.on('microphoneChange2', this.listenMicophoneAudioTrack)
|
|
|
+ // console.log(runtime.microphones, runtime.microphones.length)
|
|
|
+ // watch(runtime, () => {
|
|
|
+ // console.log('runtime changed')
|
|
|
+ // if (state.binded) return
|
|
|
+ // this.listenMicophoneAudioTrack()
|
|
|
+ // })
|
|
|
+ },
|
|
|
+ beforeUnmount() {
|
|
|
+ RuntimeUtils.runtimeEvent.off('microphoneChange', this.listenMicophoneAudioTrack)
|
|
|
+ // RuntimeUtils.runtimeEvent.off('microphoneChange2', this.listenMicophoneAudioTrack)
|
|
|
},
|
|
|
methods: {
|
|
|
+ setIconPath(iconName: string, num: number) {
|
|
|
+ const icon = (this.$refs[iconName] as HTMLSpanElement)?.querySelector(
|
|
|
+ '#microphone-content-svg'
|
|
|
+ )
|
|
|
+ if (icon) {
|
|
|
+ const line = 14 - 11 * (num / 100)
|
|
|
+ icon.setAttribute(
|
|
|
+ 'd',
|
|
|
+ `M7,${line} L14,${line} L14,10.5 C14,12.4329966 12.4329966,14 10.5,14 C8.56700338,14 7,12.4329966 7,10.5 L7,${line} L7,${line} Z`
|
|
|
+ )
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async listenMicophoneAudioTrack() {
|
|
|
+ let levelChecker: ScriptProcessorNode, levelChecker2: ScriptProcessorNode
|
|
|
+ // console.log('listenMicophoneAudioTrack', runtime.selectedMicrophone)
|
|
|
+ if (runtime.selectedMicrophone && !this.isMicrophoneDisabled) {
|
|
|
+ const mediaStreams = await requireMedia({
|
|
|
+ audio: {
|
|
|
+ deviceId: runtime.selectedMicrophone?.deviceId,
|
|
|
+ },
|
|
|
+ video: false,
|
|
|
+ })
|
|
|
+ levelChecker = RuntimeUtils.listenAudioChecker(mediaStreams, (num, label) => {
|
|
|
+ this.setIconPath('microphoneicon', num)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // @ts-ignore
|
|
|
+ if (levelChecker && levelChecker?.onaudioprocess) {
|
|
|
+ // @ts-ignore
|
|
|
+ // levelChecker?.onaudioprocess = null
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if (runtime.selectedMicrophone2 && !this.isMicrophone2Disabled) {
|
|
|
+ // const mediaStreams = await requireMedia({
|
|
|
+ // audio: {
|
|
|
+ // deviceId: runtime.selectedMicrophone2?.deviceId,
|
|
|
+ // },
|
|
|
+ // video: false,
|
|
|
+ // })
|
|
|
+ // levelChecker2 = RuntimeUtils.listenAudioChecker(mediaStreams, (num, label) => {
|
|
|
+ // console.log(num, label)
|
|
|
+ // this.setIconPath('microphoneicon2', num)
|
|
|
+ // })
|
|
|
+ // } else {
|
|
|
+ // console.log('disconnect')
|
|
|
+ // // @ts-ignore
|
|
|
+ // if (levelChecker2 && levelChecker2?.onaudioprocess) {
|
|
|
+ // // @ts-ignore
|
|
|
+ // // levelChecker2?.onaudioprocess = null
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // for (const item of runtime.microphones) {
|
|
|
+ // const mediaStreams = await requireMedia({
|
|
|
+ // audio: {
|
|
|
+ // deviceId: item.deviceId,
|
|
|
+ // },
|
|
|
+ // video: false,
|
|
|
+ // })
|
|
|
+ // const indexof = runtime.microphones.indexOf(item)
|
|
|
+ // RuntimeUtils.listenAudioChecker(mediaStreams, (num, label) => {
|
|
|
+ // // this.setIconPath('microphoneicon-' + indexof, num)
|
|
|
+ // // if (indexof === 0) {
|
|
|
+ // // this.setIconPath('microphoneicon', num)
|
|
|
+ // // this.setIconPath('microphoneicon2', num)
|
|
|
+ // // }
|
|
|
+ // // console.log(num, label, indexof, this.$refs['microphoneicon-' + indexof])
|
|
|
+ // })
|
|
|
+ // state.binded = true
|
|
|
+ // }
|
|
|
+ },
|
|
|
startShare() {
|
|
|
console.log('调用')
|
|
|
state.shareVisiable = true
|
|
@@ -41,7 +150,35 @@ export default defineComponent({
|
|
|
volumeChange(value: number) {
|
|
|
state.volume = value
|
|
|
RuntimeUtils.setVolume(value)
|
|
|
- }
|
|
|
+ },
|
|
|
+ async toggleCamera() {
|
|
|
+ RuntimeUtils.toggleDevice('camera')
|
|
|
+ if (runtime.screenShareStatus) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ state.barStatus.camera = !state.barStatus.camera
|
|
|
+ },
|
|
|
+ async toggleMicrophone() {
|
|
|
+ const needPublish = runtime.videoStatus === 'liveing'
|
|
|
+ state.barStatus.microphone = !state.barStatus.microphone
|
|
|
+ if (!state.barStatus.microphone) {
|
|
|
+ RuntimeUtils.openDevice('microphone', needPublish)
|
|
|
+ } else {
|
|
|
+ RuntimeUtils.closeDevice('microphone', needPublish)
|
|
|
+ }
|
|
|
+ this.listenMicophoneAudioTrack()
|
|
|
+ },
|
|
|
+ async toggleMicrophone2() {
|
|
|
+ const needPublish = runtime.videoStatus === 'liveing'
|
|
|
+ console.log('toggleMicrophone2', state.barStatus.microphone2)
|
|
|
+ state.barStatus.microphone2 = !state.barStatus.microphone2
|
|
|
+ if (!state.barStatus.microphone2) {
|
|
|
+ RuntimeUtils.openDevice('microphone', needPublish)
|
|
|
+ } else {
|
|
|
+ RuntimeUtils.closeDevice('microphone', needPublish)
|
|
|
+ }
|
|
|
+ this.listenMicophoneAudioTrack()
|
|
|
+ },
|
|
|
},
|
|
|
render() {
|
|
|
return (
|
|
@@ -50,42 +187,47 @@ export default defineComponent({
|
|
|
<div class={styles['bar-btn']}>
|
|
|
<div class={styles.btnInner}>
|
|
|
<SvgIcon
|
|
|
- onClick={() => {
|
|
|
- RuntimeUtils.toggleDevice('camera')
|
|
|
- if (runtime.screenShareStatus) {
|
|
|
- return
|
|
|
- }
|
|
|
- state.barStatus.camera = !state.barStatus.camera
|
|
|
- }}
|
|
|
- name={this.isCameraDisabled ? 'bar-camera-disabled' : 'bar-camera'}
|
|
|
+ onClick={this.toggleCamera}
|
|
|
+ name={
|
|
|
+ this.isCameraDisabled ? 'bar-camera-disabled' : 'bar-camera'
|
|
|
+ }
|
|
|
style={{
|
|
|
width: '22px',
|
|
|
- cursor: 'pointer'
|
|
|
+ cursor: 'pointer',
|
|
|
}}
|
|
|
/>
|
|
|
- { runtime.cameras.length === 0 ? null : <ElDropdown
|
|
|
- placement="top"
|
|
|
- // @ts-ignore
|
|
|
- disabled={runtime.cameras.length === 0}
|
|
|
- onCommand={RuntimeUtils.setSelectCamera}
|
|
|
- // @ts-ignore
|
|
|
- vSlots={{
|
|
|
- dropdown: () => (
|
|
|
- <ElDropdownMenu>
|
|
|
- {runtime.cameras.map(item => (<ElDropdownItem disabled={item === runtime.selectedCamera} command={item}>{item.label}</ElDropdownItem>))}
|
|
|
- </ElDropdownMenu>
|
|
|
- )
|
|
|
- }}
|
|
|
- >
|
|
|
- <div class={styles['bar-btn']} style={{ height: '32px' }}>
|
|
|
- <SvgIcon
|
|
|
- name="bar-arrow-down"
|
|
|
- style={{
|
|
|
- width: '18px'
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </ElDropdown> }
|
|
|
+ {runtime.cameras.length === 0 ? null : (
|
|
|
+ <ElDropdown
|
|
|
+ placement="top"
|
|
|
+ // @ts-ignore
|
|
|
+ disabled={runtime.cameras.length === 0}
|
|
|
+ onCommand={RuntimeUtils.setSelectCamera}
|
|
|
+ // @ts-ignore
|
|
|
+ vSlots={{
|
|
|
+ dropdown: () => (
|
|
|
+ <ElDropdownMenu>
|
|
|
+ {runtime.cameras.map((item) => (
|
|
|
+ <ElDropdownItem
|
|
|
+ disabled={item === runtime.selectedCamera}
|
|
|
+ command={item}
|
|
|
+ >
|
|
|
+ {item.label}
|
|
|
+ </ElDropdownItem>
|
|
|
+ ))}
|
|
|
+ </ElDropdownMenu>
|
|
|
+ ),
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class={styles['bar-btn']} style={{ height: '32px' }}>
|
|
|
+ <SvgIcon
|
|
|
+ name="bar-arrow-down"
|
|
|
+ style={{
|
|
|
+ width: '18px',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </ElDropdown>
|
|
|
+ )}
|
|
|
</div>
|
|
|
<span class={styles['bar-btn-text']}>摄像头</span>
|
|
|
</div>
|
|
@@ -133,18 +275,27 @@ export default defineComponent({
|
|
|
<span class={styles['bar-btn-text']}>音量调节</span>
|
|
|
</div> */}
|
|
|
|
|
|
- <div class={styles['bar-btn']} onClick={RuntimeUtils.toggleShareScreenVideo}>
|
|
|
+ {/* <div
|
|
|
+ class={styles['bar-btn']}
|
|
|
+ onClick={RuntimeUtils.toggleShareScreenVideo}
|
|
|
+ >
|
|
|
<div class={styles.btnInner}>
|
|
|
<SvgIcon
|
|
|
- name={runtime.videoStatus === 'liveing' ? 'bar-screen-share' : 'bar-screen-share-disabled2'}
|
|
|
+ name={
|
|
|
+ runtime.videoStatus === 'liveing'
|
|
|
+ ? 'bar-screen-share'
|
|
|
+ : 'bar-screen-share-disabled2'
|
|
|
+ }
|
|
|
style={{
|
|
|
width: '22px',
|
|
|
- cursor: 'pointer'
|
|
|
+ cursor: 'pointer',
|
|
|
}}
|
|
|
/>
|
|
|
</div>
|
|
|
- <span class={styles['bar-btn-text']}>{runtime.screenShareStatus ? '取消共享' : '屏幕共享'}</span>
|
|
|
- </div>
|
|
|
+ <span class={styles['bar-btn-text']}>
|
|
|
+ {runtime.screenShareStatus ? '取消共享' : '屏幕共享'}
|
|
|
+ </span>
|
|
|
+ </div> */}
|
|
|
|
|
|
{/* <div class={styles['bar-btn']} >
|
|
|
<div class={styles.btnInner}>
|
|
@@ -160,72 +311,168 @@ export default defineComponent({
|
|
|
</div> */}
|
|
|
<div class={styles['bar-btn']}>
|
|
|
<div class={styles.btnInner}>
|
|
|
+ {this.isMicrophoneDisabled ? (
|
|
|
+ <SvgIcon
|
|
|
+ name="bar-mike-disabled"
|
|
|
+ onClick={this.toggleMicrophone}
|
|
|
+ style={{
|
|
|
+ width: '22px',
|
|
|
+ height: '22px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <span
|
|
|
+ ref="microphoneicon"
|
|
|
+ v-html={MicroPhoneIcon}
|
|
|
+ onClick={this.toggleMicrophone}
|
|
|
+ // name={this.isMicrophoneDisabled ? 'bar-mike-disabled' : 'bar-mike'}
|
|
|
+ style={{
|
|
|
+ width: '22px',
|
|
|
+ height: '22px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ {runtime.microphones.length === 0 ? null : (
|
|
|
+ <ElDropdown
|
|
|
+ placement="top-start"
|
|
|
+ // @ts-ignore
|
|
|
+ disabled={runtime.microphones.length === 0}
|
|
|
+ popper-options={{
|
|
|
+ boundariesElement: '#action-bar',
|
|
|
+ gpuAcceleration: false,
|
|
|
+ }}
|
|
|
+ visible-change={(visible: boolean) => {
|
|
|
+ console.log(visible)
|
|
|
+ }}
|
|
|
+ onCommand={RuntimeUtils.setSelectMicrophone}
|
|
|
+ // @ts-ignore
|
|
|
+ vSlots={{
|
|
|
+ dropdown: () => (
|
|
|
+ <ElDropdownMenu>
|
|
|
+ {runtime.microphones.map((item, index) => (
|
|
|
+ <ElDropdownItem
|
|
|
+ disabled={
|
|
|
+ item === runtime.selectedMicrophone ||
|
|
|
+ item === runtime.selectedMicrophone2
|
|
|
+ }
|
|
|
+ command={item}
|
|
|
+ >
|
|
|
+ {/* <span
|
|
|
+ ref={'microphoneicon-' + index}
|
|
|
+ v-html={MicroPhoneIcon}
|
|
|
+ // name={this.isMicrophoneDisabled ? 'bar-mike-disabled' : 'bar-mike'}
|
|
|
+ style={{
|
|
|
+ width: '22px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ }}
|
|
|
+ /> */}
|
|
|
+ {' ' + item.label}
|
|
|
+ </ElDropdownItem>
|
|
|
+ ))}
|
|
|
+ </ElDropdownMenu>
|
|
|
+ ),
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class={styles['bar-btn']} style={{ height: '32px' }}>
|
|
|
+ <SvgIcon
|
|
|
+ name="bar-arrow-down"
|
|
|
+ style={{
|
|
|
+ width: '18px',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </ElDropdown>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <span class={styles['bar-btn-text']}>麦克风</span>
|
|
|
+ </div>
|
|
|
+ {/* <div class={styles['bar-btn']}>
|
|
|
+ <div class={styles.btnInner}>
|
|
|
<SvgIcon
|
|
|
- onClick={() => {
|
|
|
- const needPublish = runtime.videoStatus === 'liveing'
|
|
|
- state.barStatus.microphone = !state.barStatus.microphone
|
|
|
- if (!state.barStatus.microphone) {
|
|
|
- RuntimeUtils.openDevice('microphone', needPublish)
|
|
|
- } else {
|
|
|
- RuntimeUtils.closeDevice('microphone', needPublish)
|
|
|
- }
|
|
|
- }}
|
|
|
- name={this.isMicrophoneDisabled ? 'bar-mike-disabled' : 'bar-mike'}
|
|
|
+ onClick={this.toggleMicrophone2}
|
|
|
style={{
|
|
|
width: '22px',
|
|
|
- cursor: 'pointer'
|
|
|
+ height: '22px',
|
|
|
+ cursor: 'pointer',
|
|
|
}}
|
|
|
+ name={this.isMicrophone2Disabled ? 'bar-volume-disabled' : 'bar-volume'}
|
|
|
/>
|
|
|
- { runtime.microphones.length === 0 ? null : <ElDropdown
|
|
|
- placement="top-start"
|
|
|
- // @ts-ignore
|
|
|
- disabled={runtime.microphones.length === 0}
|
|
|
- popper-options={{ boundariesElement: '#action-bar', gpuAcceleration: false }}
|
|
|
- onCommand={RuntimeUtils.setSelectMicrophone}
|
|
|
- // @ts-ignore
|
|
|
- vSlots={{
|
|
|
- dropdown: () => (
|
|
|
- <ElDropdownMenu>
|
|
|
- {runtime.microphones.map(item => (<ElDropdownItem disabled={item === runtime.selectedMicrophone} command={item}>{item.label}</ElDropdownItem>))}
|
|
|
- </ElDropdownMenu>
|
|
|
- )
|
|
|
- }}
|
|
|
- >
|
|
|
- <div class={styles['bar-btn']} style={{ height: '32px' }}>
|
|
|
- <SvgIcon
|
|
|
- name="bar-arrow-down"
|
|
|
- style={{
|
|
|
- width: '18px'
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </ElDropdown>}
|
|
|
+ {runtime.microphones.length === 0 ? null : (
|
|
|
+ <ElDropdown
|
|
|
+ placement="top-start"
|
|
|
+ // @ts-ignore
|
|
|
+ disabled={runtime.microphones.length === 0}
|
|
|
+ popper-options={{
|
|
|
+ boundariesElement: '#action-bar',
|
|
|
+ gpuAcceleration: false,
|
|
|
+ }}
|
|
|
+ visible-change={(visible: boolean) => {
|
|
|
+ console.log(visible)
|
|
|
+ }}
|
|
|
+ onCommand={RuntimeUtils.setSelectMicrophone2}
|
|
|
+ // @ts-ignore
|
|
|
+ vSlots={{
|
|
|
+ dropdown: () => (
|
|
|
+ <ElDropdownMenu>
|
|
|
+ {runtime.microphones.map((item, index) => (
|
|
|
+ <ElDropdownItem
|
|
|
+ disabled={
|
|
|
+ item === runtime.selectedMicrophone2 ||
|
|
|
+ item === runtime.selectedMicrophone
|
|
|
+ }
|
|
|
+ command={item}
|
|
|
+ >
|
|
|
+ {' ' + item.label}
|
|
|
+ </ElDropdownItem>
|
|
|
+ ))}
|
|
|
+ </ElDropdownMenu>
|
|
|
+ ),
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class={styles['bar-btn']} style={{ height: '32px' }}>
|
|
|
+ <SvgIcon
|
|
|
+ name="bar-arrow-down"
|
|
|
+ style={{
|
|
|
+ width: '18px',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </ElDropdown>
|
|
|
+ )}
|
|
|
</div>
|
|
|
- <span class={styles['bar-btn-text']}>麦克风</span>
|
|
|
- </div>
|
|
|
-
|
|
|
+ <span class={styles['bar-btn-text']}>系统音频</span>
|
|
|
+ </div> */}
|
|
|
</div>
|
|
|
<div style={{ display: 'flex' }} onClick={this.startShare}>
|
|
|
- <div class={styles['bar-btn']} >
|
|
|
+ <div class={styles['bar-btn']}>
|
|
|
<div class={styles.btnInner}>
|
|
|
<SvgIcon
|
|
|
name="bar-share"
|
|
|
style={{
|
|
|
width: '22px',
|
|
|
- cursor: 'pointer'
|
|
|
+ cursor: 'pointer',
|
|
|
}}
|
|
|
/>
|
|
|
</div>
|
|
|
- <span class={styles['bar-btn-text']} >分享</span>
|
|
|
+ <span class={styles['bar-btn-text']}>分享</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
{/* <ElButton onClick={RuntimeUtils.shareScreenVideo}>屏幕共享</ElButton> */}
|
|
|
- <ElDialog width="510px"
|
|
|
- destroy-on-close
|
|
|
- append-to-body modelValue={state.shareVisiable} title="分享" before-close={() => { state.shareVisiable = false }}>
|
|
|
- <Share onClose={()=>state.shareVisiable = false}/>
|
|
|
+ <ElDialog
|
|
|
+ width="510px"
|
|
|
+ destroy-on-close
|
|
|
+ append-to-body
|
|
|
+ modelValue={state.shareVisiable}
|
|
|
+ title="分享"
|
|
|
+ before-close={() => {
|
|
|
+ state.shareVisiable = false
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Share onClose={() => (state.shareVisiable = false)} />
|
|
|
</ElDialog>
|
|
|
</div>
|
|
|
)
|
|
|
- }
|
|
|
+ },
|
|
|
})
|