浏览代码

feat: 新增 Audio 组件 (#1003)

* feat: 新增 audio

Co-authored-by: yangxiaolu3 <yangxiaolu1993@qq.com>
yangxiaolu1993 3 年之前
父节点
当前提交
3acf5e2d22

+ 23 - 0
src/config.json

@@ -198,6 +198,7 @@
           "tarodoc": true,
           "author": "zongyue3"
         }
+        
       ]
     },
     {
@@ -479,6 +480,28 @@
           "sort": 24,
           "show": true,
           "author": "wujia8"
+        },
+        {
+          "version": "3.0.0",
+          "name": "Audio",
+          "taro": true,
+          "tarodoc": true,
+          "type": "component",
+          "cName": "音频播放器",
+          "desc": "音频播放器",
+          "sort": 25,
+          "show": true,
+          "author": "yangxiaolu"
+        },
+        {
+          "version": "3.0.0",
+          "name": "AudioOperate",
+          "type": "component",
+          "cName": "音频操作按钮",
+          "desc": "音频操作按钮",
+          "sort": 26,
+          "show": false,
+          "author": "yangxiaolu"
         }
       ]
     },

+ 131 - 0
src/packages/__VUE/audio/demo.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="demo">
+    <h2>基础用法</h2>
+    <nut-audio
+      style="margin-left: 20px"
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="true"
+      type="icon"
+    ></nut-audio>
+
+    <h2>语音播放</h2>
+    <nut-audio
+      style="margin-left: 20px"
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="false"
+      type="none"
+      ref="audioDemo"
+    >
+      <div class="nut-voice">
+        <div><nut-icon name="voice"></nut-icon></div>
+        <div>{{ duration }}"</div>
+      </div>
+    </nut-audio>
+
+    <h2>进度条展示</h2>
+    <nut-audio
+      style="margin-left: 20px"
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="true"
+      type="progress"
+    >
+      <div class="nut-audio-operate-group">
+        <nut-audio-operate type="back"></nut-audio-operate>
+        <nut-audio-operate type="play"></nut-audio-operate>
+        <nut-audio-operate type="forward"></nut-audio-operate>
+        <nut-audio-operate type="mute"></nut-audio-operate>
+      </div>
+    </nut-audio>
+
+    <h2>自定义操作按钮</h2>
+    <nut-audio
+      style="margin-left: 20px"
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="false"
+      type="progress"
+      @forward="forward"
+      @fastBack="fastBack"
+      @play="changeStatus"
+      @ended="ended"
+      @changeProgress="changeProgress"
+    >
+      <div class="nut-audio-operate-group">
+        <nut-audio-operate type="back"><nut-icon name="play-double-back" size="35"></nut-icon></nut-audio-operate>
+        <nut-audio-operate type="play"
+          ><nut-icon :name="!playing ? 'play-start' : 'play-stop'" size="35"></nut-icon
+        ></nut-audio-operate>
+        <nut-audio-operate type="forward"><nut-icon name="play-double-forward" size="35"></nut-icon></nut-audio-operate>
+      </div>
+    </nut-audio>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, ref } from '@vue/reactivity';
+import { onMounted } from '@vue/runtime-core';
+import { createComponent } from '../../utils/create';
+const { createDemo } = createComponent('audio');
+export default createDemo({
+  props: {},
+  setup() {
+    const audioDemo = ref(null);
+    const playing = ref(false);
+    const duration = ref(0);
+    const data = reactive({
+      muted: false,
+      autoplay: false
+    });
+
+    const fastBack = () => {
+      console.log('倒退');
+    };
+
+    const forward = (progress) => {
+      console.log('快进', '当前时间' + progress);
+    };
+
+    const changeStatus = (status) => {
+      console.log('当前播放状态', status);
+      playing.value = status;
+    };
+
+    const ended = () => {
+      console.log('播放结束');
+    };
+
+    const changeProgress = (val) => {
+      console.log('改变进度条', val);
+    };
+
+    onMounted(() => {
+      console.log(audioDemo.value);
+      setTimeout(() => {
+        duration.value = audioDemo.value.second.toFixed();
+      }, 500);
+    });
+
+    return { ...toRefs(data), playing, fastBack, forward, changeStatus, audioDemo, ended, duration, changeProgress };
+  }
+});
+</script>
+<style lang="scss" scoped>
+.demo {
+  .nut-voice {
+    display: flex;
+    justify-content: space-between;
+    width: 100px;
+    height: 20px;
+    padding: 8px;
+    border: 1px solid rgba(0, 0, 0, 0.6);
+    border-radius: 18px;
+  }
+}
+</style>

+ 236 - 0
src/packages/__VUE/audio/doc.md

@@ -0,0 +1,236 @@
+# Audio组件
+
+### 介绍
+
+用于音频播放
+
+### 安装
+
+
+```javascript
+import { createApp } from 'vue';
+// vue
+import { Audio} from '@nutui/nutui';
+
+const app = createApp();
+app.use(Audio);
+```
+
+### 基础用法
+
+:::demo
+
+```html
+<template>
+    <nut-audio
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="true"
+      type="icon"
+    ></nut-audio>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const data = reactive({
+      muted: false,
+      autoplay: false
+    });
+    return {
+      ...toRefs(data)
+    };
+  }
+};
+</script>
+```
+:::
+
+### 语音播放
+
+:::demo
+
+```html
+<template>
+    <nut-audio
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="false"
+      type="none"
+    >
+      <div class="nut-voice">
+        <div><nut-icon name="voice"></nut-icon></div>
+        <div>{{ duration }}"</div>
+      </div>
+    </nut-audio>
+</template>
+<script lang="ts">
+import { reactive, toRefs, onMounted } from 'vue';
+export default {
+  setup() {
+    const data = reactive({
+      muted: false,
+      autoplay: false
+    });
+    const duration = ref(0);
+    onMounted(() => {
+      console.log(audioDemo.value);
+      setTimeout(() => {
+        duration.value = audioDemo.value.second.toFixed();
+      }, 500);
+    });
+
+    return {
+      ...toRefs(data),
+      duration
+    };
+  }
+};
+</script>
+
+<style>
+  .nut-voice {
+    display: flex;
+    justify-content: space-between;
+    width: 100px;
+    height: 20px;
+    padding: 8px;
+    border: 1px solid rgba(0, 0, 0, 0.6);
+    border-radius: 18px;
+  }
+</style>
+```
+:::
+
+### 进度条展示
+
+:::demo
+
+```html
+<template>
+    <nut-audio
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="true"
+      type="progress"
+    >
+      <div class="nut-audio-operate-group">
+        <nut-audio-operate type="back"></nut-audio-operate>
+        <nut-audio-operate type="play"></nut-audio-operate>
+        <nut-audio-operate type="forward"></nut-audio-operate>
+        <nut-audio-operate type="mute"></nut-audio-operate>
+      </div>
+    </nut-audio>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const data = reactive({
+      muted: false,
+      autoplay: false
+    });
+    return {
+      ...toRefs(data)
+    };
+  }
+};
+</script>
+```
+:::
+
+### 自定义操作按钮
+
+:::demo
+
+```html
+<template>
+    <nut-audio
+      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      :muted="muted"
+      :autoplay="autoplay"
+      :loop="false"
+      type="progress"
+      @forward="forward"
+      @fastBack="fastBack"
+      @play="changeStatus"
+      @ended="ended"
+      @changeProgress="changeProgress"
+    >
+      <div class="nut-audio-operate-group">
+        <nut-audio-operate type="back"><nut-icon name="play-double-back" size="35"></nut-icon></nut-audio-operate>
+        <nut-audio-operate type="play"
+          ><nut-icon :name="!playing ? 'play-start' : 'play-stop'" size="35"></nut-icon
+        ></nut-audio-operate>
+        <nut-audio-operate type="forward"><nut-icon name="play-double-forward" size="35"></nut-icon></nut-audio-operate>
+      </div>
+    </nut-audio>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const data = reactive({
+      muted: false,
+      autoplay: false
+    });
+    const playing = ref(false);
+
+    const fastBack = () => {
+      console.log('倒退');
+    };
+
+    const forward = (progress) => {
+      console.log('快进', '当前时间' + progress);
+    };
+
+    const changeStatus = (status) => {
+      console.log('当前播放状态', status);
+      playing.value = status;
+    };
+
+    const ended = () => {
+      console.log('播放结束');
+    };
+
+    const changeProgress = (val) => {
+      console.log('改变进度条', val);
+    };
+    return {
+      ...toRefs(data),playing, fastBack, forward, changeStatus, audioDemo, ended, duration, changeProgress
+    };
+  }
+};
+</script>
+```
+:::
+
+## API
+
+### Props
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| url         | 语音播放的连接               | String | ''              |
+| muted        | 是否静音                         | Boolean | false             |
+| autoplay         | 是否自动播放 | Boolean | false               |
+| loop | 是否循环播放     | Boolean | false |
+| preload          | 是否预加载语音                        | String | 'auto'              |
+| type         | 展示形式,可选值:controls 控制面板   progress 进度条  icon 图标 none 自定义 | String | 'progress'              |
+
+
+### Events
+
+| 事件名 | 说明           | 回调参数     |
+|--------|----------------|--------------|
+| fastBack  | 触发语音快退 | 返回当前播放时长(单位:毫秒) |
+| forward  | 触发语音快进 | 返回当前播放时长(单位:毫秒) |
+| play  | 触发播放/暂停语音 | 返回当前播放状态 |
+| ended  | 语音播放完成,当loop设置为false时生效 | —— |
+| mute  | 触发静音 | —— |
+| changeProgress  | 当进度条改变时触发 | 返回当前播放时长(单位:毫秒) |
+
+    

+ 7 - 0
src/packages/__VUE/audio/doc.taro.md

@@ -0,0 +1,7 @@
+# Audio组件
+
+### 介绍
+
+用于音频播放
+
+#### 直接使用 Taro 现有的 Taro.createInnerAudioContext 接口开发 [参考文档](https://taro-docs.jd.com/taro/docs/apis/media/audio/createInnerAudioContext)

+ 66 - 0
src/packages/__VUE/audio/index.scss

@@ -0,0 +1,66 @@
+.nut-audio {
+  padding: 0;
+  .progress-wrapper {
+    display: flex;
+    align-items: center;
+    width: 100%;
+    margin: 0px auto;
+    padding: 10px 0;
+
+    .progress-bar-wrapper {
+      flex: 1;
+      margin: 0 10px;
+    }
+
+    .time {
+      min-width: 50px;
+      font-size: 12px;
+      text-align: center;
+    }
+  }
+
+  .nut-audio-icon {
+    position: relative;
+    display: inline-block;
+
+    .nut-audio-icon-box {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 30px;
+      height: 30px;
+      background: #fff;
+      border-radius: 50%;
+      box-shadow: 0 0 8px rgba($color: $text-color, $alpha: 0.5);
+      &.nut-audio-icon-stop {
+        &::after {
+          position: absolute;
+          left: 50%;
+          top: 50%;
+          transform: translateX(-15px);
+          content: '';
+          height: 2px;
+          width: 30px;
+          background: $disable-color;
+          transform: rotate(45deg);
+          transform-origin: 8px -18px;
+        }
+      }
+    }
+  }
+
+  .audioMain {
+    margin-top: 30px;
+  }
+
+  .custom-button {
+    width: 8px;
+    height: 8px;
+    color: #fff;
+    font-size: 10px;
+    line-height: 18px;
+    text-align: center;
+    background-color: #ee0a24;
+    border-radius: 100px;
+  }
+}

+ 302 - 0
src/packages/__VUE/audio/index.taro.vue

@@ -0,0 +1,302 @@
+<template>
+  <!-- 显示进度条 、 播放时长、 兼容是否支持 、暂停、 开启-->
+
+  <view class="nut-audio">
+    <!-- 进度条 -->
+    <view class="progress-wrapper" v-if="type == 'progress'">
+      <!-- 时间显示 -->
+      <view class="time">{{ currentDuration }}</view>
+      <view class="progress-bar-wrapper">
+        <nut-range
+          v-model="percent"
+          hidden-range
+          @change="progressChange"
+          inactive-color="#cccccc"
+          active-color="#fa2c19"
+        >
+          <template #button>
+            <view class="custom-button"></view>
+          </template>
+        </nut-range>
+      </view>
+
+      <view class="time">{{ duration }}</view>
+    </view>
+
+    <!-- 自定义 -->
+    <view class="nut-audio-icon" v-if="type == 'icon'">
+      <view
+        :class="['nut-audio-icon-box', playing ? 'nut-audio-icon-play' : 'nut-audio-icon-stop']"
+        @click="changeStatus"
+      >
+        <nut-icon v-if="playing" name="service" class="nut-icon-am-rotate nut-icon-am-infinite"></nut-icon>
+        <nut-icon v-if="!playing" name="service"></nut-icon>
+      </view>
+    </view>
+
+    <view v-if="type == 'none'" @click="changeStatus">
+      <slot></slot>
+    </view>
+
+    <!-- 操作按钮 -->
+    <template v-if="type != 'none'">
+      <slot></slot>
+    </template>
+
+    <audio
+      class="audioMain"
+      :controls="type == 'controls'"
+      ref="audioRef"
+      :src="url"
+      :preload="preload"
+      :autoplay="autoplay"
+      :loop="loop"
+      @timeupdate="onTimeupdate"
+      @ended="audioEnd"
+      :muted="hanMuted"
+    >
+    </audio>
+  </view>
+</template>
+<script lang="ts">
+import { toRefs, ref, onMounted, reactive, watch, provide } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('audio');
+
+export default create({
+  props: {
+    url: {
+      type: String,
+      default() {
+        return '';
+      }
+    },
+    // 静音
+    muted: {
+      type: Boolean,
+      default() {
+        return false;
+      }
+    },
+    // 自动播放
+    autoplay: {
+      type: Boolean,
+      default() {
+        return false;
+      }
+    },
+
+    // 循环播放
+    loop: {
+      type: Boolean,
+      default() {
+        return false;
+      }
+    },
+
+    // 是否预加载音频
+    preload: {
+      type: String,
+      default() {
+        return 'auto';
+      }
+    },
+    /* 总时长秒数 */
+    second: {
+      type: Number,
+      default() {
+        return 0;
+      }
+    },
+
+    // 展示的形式   controls 控制面板   progress 进度条  icon 图标 none 自定义
+    type: {
+      type: String,
+      default() {
+        return 'progress';
+      }
+    }
+  },
+  components: {},
+  emits: ['fastBack', 'play', 'forward', 'ended', 'changeProgress', 'mute'],
+
+  setup(props, { emit }) {
+    const audioRef = ref(null);
+
+    const audioData = reactive({
+      currentTime: 0,
+      currentDuration: '00:00:00',
+      percent: 0,
+      duration: '00:00:00',
+      second: 0,
+      hanMuted: props.muted,
+      playing: props.autoplay
+    });
+
+    onMounted(() => {
+      // 播放的兼容性
+      var arr = ['webkitVisibilityState', 'visibilitychange'];
+      try {
+        for (let i = 0; i < arr.length; i++) {
+          document.addEventListener(arr[i], () => {
+            if (document.hidden) {
+              // 页面被挂起
+              // 这里要根据用户当前播放状态,做音频暂停操作
+              (audioRef.value as any).pause();
+            } else {
+              // 页面呼出
+              if (audioData.playing) {
+                setTimeout(() => {
+                  // 这里要 根据页面挂起前音频的播放状态,做音频播放操作
+                  (audioRef.value as any).play();
+                }, 200);
+              }
+            }
+          });
+        }
+      } catch (e) {
+        console.log((e as any).message);
+      }
+
+      // 获取当前音频播放时长
+      setTimeout(() => {
+        // 自动播放
+        if (props.autoplay) {
+          if (audioRef.value && audioRef.value.paused) {
+            audioRef.value.play();
+          }
+        }
+        audioData.second = audioRef.value.duration;
+        audioData.duration = formatSeconds(audioRef.value.duration);
+      }, 500);
+    });
+
+    //播放时间
+    const onTimeupdate = (e) => {
+      audioData.currentTime = parseInt(e.target.currentTime);
+    };
+
+    //后退
+    const fastBack = () => {
+      audioData.currentTime--;
+      audioRef.value.currentTime = audioData.currentTime;
+
+      emit('fastBack', audioData.currentTime);
+    };
+
+    //改变播放状态
+    const changeStatus = () => {
+      if (audioData.playing) {
+        audioRef.value.pause();
+
+        audioData.handPlaying = false;
+      } else {
+        audioRef.value.play();
+        audioData.handPlaying = true;
+      }
+      audioData.playing = !audioData.playing;
+
+      emit('play', audioData.playing);
+    };
+
+    //快进
+    const forward = () => {
+      audioData.currentTime++;
+      audioRef.value.currentTime = audioData.currentTime;
+
+      emit('forward', audioData.currentTime);
+    };
+
+    //处理
+    const handle = (val) => {
+      //毫秒数转为时分秒
+      audioData.currentDuration = formatSeconds(val);
+      audioData.percent = (val / audioData.second) * 100;
+    };
+    //播放结束 修改播放状态
+    const audioEnd = () => {
+      audioData.playing = false;
+      emit('ended');
+    };
+
+    //点击进度条
+    const progressChange = (val) => {
+      audioRef.value.currentTime = (audioData.second * val) / 100;
+
+      emit('changeProgress', audioRef.value.currentTime);
+    };
+
+    // 静音
+    const handleMute = () => {
+      audioData.hanMuted = !audioData.hanMuted;
+
+      emit('mute', audioData.hanMuted);
+    };
+
+    const formatSeconds = (value) => {
+      let theTime = parseInt(value); // 秒
+      let theTime1 = 0; // 分
+      let theTime2 = 0; // 小时
+      if (theTime > 60) {
+        theTime1 = parseInt(theTime / 60);
+        theTime = parseInt(theTime % 60);
+        if (theTime1 > 60) {
+          theTime2 = parseInt(theTime1 / 60);
+          theTime1 = parseInt(theTime1 % 60);
+        }
+      }
+      let result = '' + parseInt(theTime);
+      if (result < 10) {
+        result = '0' + result;
+      }
+      if (theTime1 > 0) {
+        result = '' + parseInt(theTime1) + ':' + result;
+        if (theTime1 < 10) {
+          result = '0' + result;
+        }
+      } else {
+        result = '00:' + result;
+      }
+      if (theTime2 > 0) {
+        result = '' + parseInt(theTime2) + ':' + result;
+        if (theTime2 < 10) {
+          result = '0' + result;
+        }
+      } else {
+        result = '00:' + result;
+      }
+      return result;
+    };
+
+    watch(
+      () => audioData.currentTime,
+      (value) => {
+        handle(value);
+      }
+    );
+
+    provide('audioParent', {
+      children: [],
+      props,
+      audioData,
+      handleMute,
+      forward,
+      fastBack,
+      changeStatus
+    });
+
+    return {
+      ...toRefs(props),
+      ...toRefs(audioData),
+      audioRef,
+      fastBack,
+      forward,
+      changeStatus,
+      progressChange,
+      audioEnd,
+      onTimeupdate,
+      handleMute
+    };
+  }
+});
+</script>

+ 302 - 0
src/packages/__VUE/audio/index.vue

@@ -0,0 +1,302 @@
+<template>
+  <!-- 显示进度条 、 播放时长、 兼容是否支持 、暂停、 开启-->
+
+  <div class="nut-audio">
+    <!-- 进度条 -->
+    <div class="progress-wrapper" v-if="type == 'progress'">
+      <!-- 时间显示 -->
+      <div class="time">{{ currentDuration }}</div>
+      <div class="progress-bar-wrapper">
+        <nut-range
+          v-model="percent"
+          hidden-range
+          @change="progressChange"
+          inactive-color="#cccccc"
+          active-color="#fa2c19"
+        >
+          <template #button>
+            <div class="custom-button"></div>
+          </template>
+        </nut-range>
+      </div>
+
+      <div class="time">{{ duration }}</div>
+    </div>
+
+    <!-- 自定义 -->
+    <div class="nut-audio-icon" v-if="type == 'icon'">
+      <div
+        :class="['nut-audio-icon-box', playing ? 'nut-audio-icon-play' : 'nut-audio-icon-stop']"
+        @click="changeStatus"
+      >
+        <nut-icon v-if="playing" name="service" class="nut-icon-am-rotate nut-icon-am-infinite"></nut-icon>
+        <nut-icon v-if="!playing" name="service"></nut-icon>
+      </div>
+    </div>
+
+    <div v-if="type == 'none'" @click="changeStatus">
+      <slot></slot>
+    </div>
+
+    <!-- 操作按钮 -->
+    <template v-if="type != 'none'">
+      <slot></slot>
+    </template>
+
+    <audio
+      class="audioMain"
+      :controls="type == 'controls'"
+      ref="audioRef"
+      :src="url"
+      :preload="preload"
+      :autoplay="autoplay"
+      :loop="loop"
+      @timeupdate="onTimeupdate"
+      @ended="audioEnd"
+      :muted="hanMuted"
+    >
+    </audio>
+  </div>
+</template>
+<script lang="ts">
+import { toRefs, ref, onMounted, reactive, watch, provide } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('audio');
+
+export default create({
+  props: {
+    url: {
+      type: String,
+      default() {
+        return '';
+      }
+    },
+    // 静音
+    muted: {
+      type: Boolean,
+      default() {
+        return false;
+      }
+    },
+    // 自动播放
+    autoplay: {
+      type: Boolean,
+      default() {
+        return false;
+      }
+    },
+
+    // 循环播放
+    loop: {
+      type: Boolean,
+      default() {
+        return false;
+      }
+    },
+
+    // 是否预加载音频
+    preload: {
+      type: String,
+      default() {
+        return 'auto';
+      }
+    },
+    /* 总时长秒数 */
+    second: {
+      type: Number,
+      default() {
+        return 0;
+      }
+    },
+
+    // 展示的形式   controls 控制面板   progress 进度条  icon 图标 none 自定义
+    type: {
+      type: String,
+      default() {
+        return 'progress';
+      }
+    }
+  },
+  components: {},
+  emits: ['fastBack', 'play', 'forward', 'ended', 'changeProgress', 'mute'],
+
+  setup(props, { emit }) {
+    const audioRef = ref(null);
+
+    const audioData = reactive({
+      currentTime: 0,
+      currentDuration: '00:00:00',
+      percent: 0,
+      duration: '00:00:00',
+      second: 0,
+      hanMuted: props.muted,
+      playing: props.autoplay
+    });
+
+    onMounted(() => {
+      // 播放的兼容性
+      var arr = ['webkitVisibilityState', 'visibilitychange'];
+      try {
+        for (let i = 0; i < arr.length; i++) {
+          document.addEventListener(arr[i], () => {
+            if (document.hidden) {
+              // 页面被挂起
+              // 这里要根据用户当前播放状态,做音频暂停操作
+              (audioRef.value as any).pause();
+            } else {
+              // 页面呼出
+              if (audioData.playing) {
+                setTimeout(() => {
+                  // 这里要 根据页面挂起前音频的播放状态,做音频播放操作
+                  (audioRef.value as any).play();
+                }, 200);
+              }
+            }
+          });
+        }
+      } catch (e) {
+        console.log((e as any).message);
+      }
+
+      // 获取当前音频播放时长
+      setTimeout(() => {
+        // 自动播放
+        if (props.autoplay) {
+          if (audioRef.value && audioRef.value.paused) {
+            audioRef.value.play();
+          }
+        }
+        audioData.second = audioRef.value.duration;
+        audioData.duration = formatSeconds(audioRef.value.duration);
+      }, 500);
+    });
+
+    //播放时间
+    const onTimeupdate = (e) => {
+      audioData.currentTime = parseInt(e.target.currentTime);
+    };
+
+    //后退
+    const fastBack = () => {
+      audioData.currentTime--;
+      audioRef.value.currentTime = audioData.currentTime;
+
+      emit('fastBack', audioData.currentTime);
+    };
+
+    //改变播放状态
+    const changeStatus = () => {
+      if (audioData.playing) {
+        audioRef.value.pause();
+
+        audioData.handPlaying = false;
+      } else {
+        audioRef.value.play();
+        audioData.handPlaying = true;
+      }
+      audioData.playing = !audioData.playing;
+
+      emit('play', audioData.playing);
+    };
+
+    //快进
+    const forward = () => {
+      audioData.currentTime++;
+      audioRef.value.currentTime = audioData.currentTime;
+
+      emit('forward', audioData.currentTime);
+    };
+
+    //处理
+    const handle = (val) => {
+      //毫秒数转为时分秒
+      audioData.currentDuration = formatSeconds(val);
+      audioData.percent = (val / audioData.second) * 100;
+    };
+    //播放结束 修改播放状态
+    const audioEnd = () => {
+      audioData.playing = false;
+      emit('ended');
+    };
+
+    //点击进度条
+    const progressChange = (val) => {
+      audioRef.value.currentTime = (audioData.second * val) / 100;
+
+      emit('changeProgress', audioRef.value.currentTime);
+    };
+
+    // 静音
+    const handleMute = () => {
+      audioData.hanMuted = !audioData.hanMuted;
+
+      emit('mute', audioData.hanMuted);
+    };
+
+    const formatSeconds = (value) => {
+      let theTime = parseInt(value); // 秒
+      let theTime1 = 0; // 分
+      let theTime2 = 0; // 小时
+      if (theTime > 60) {
+        theTime1 = parseInt(theTime / 60);
+        theTime = parseInt(theTime % 60);
+        if (theTime1 > 60) {
+          theTime2 = parseInt(theTime1 / 60);
+          theTime1 = parseInt(theTime1 % 60);
+        }
+      }
+      let result = '' + parseInt(theTime);
+      if (result < 10) {
+        result = '0' + result;
+      }
+      if (theTime1 > 0) {
+        result = '' + parseInt(theTime1) + ':' + result;
+        if (theTime1 < 10) {
+          result = '0' + result;
+        }
+      } else {
+        result = '00:' + result;
+      }
+      if (theTime2 > 0) {
+        result = '' + parseInt(theTime2) + ':' + result;
+        if (theTime2 < 10) {
+          result = '0' + result;
+        }
+      } else {
+        result = '00:' + result;
+      }
+      return result;
+    };
+
+    watch(
+      () => audioData.currentTime,
+      (value) => {
+        handle(value);
+      }
+    );
+
+    provide('audioParent', {
+      children: [],
+      props,
+      audioData,
+      handleMute,
+      forward,
+      fastBack,
+      changeStatus
+    });
+
+    return {
+      ...toRefs(props),
+      ...toRefs(audioData),
+      audioRef,
+      fastBack,
+      forward,
+      changeStatus,
+      progressChange,
+      audioEnd,
+      onTimeupdate,
+      handleMute
+    };
+  }
+});
+</script>

+ 32 - 0
src/packages/__VUE/audiooperate/doc.md

@@ -0,0 +1,32 @@
+# audiooperate组件
+
+    ### 介绍
+    
+    基于 xxxxxxx
+    
+    ### 安装
+    
+    
+    
+    ### 基础用法
+    
+
+    
+    ## API
+    
+    ### Props
+    
+    | 参数         | 说明                             | 类型   | 默认值           |
+    |--------------|----------------------------------|--------|------------------|
+    | name         | 图标名称或图片链接               | String | -                |
+    | color        | 图标颜色                         | String | -                |
+    | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
+    | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
+    | tag          | HTML 标签                        | String | 'i'              |
+    
+    ### Events
+    
+    | 事件名 | 说明           | 回调参数     |
+    |--------|----------------|--------------|
+    | click  | 点击图标时触发 | event: Event |
+    

+ 13 - 0
src/packages/__VUE/audiooperate/index.scss

@@ -0,0 +1,13 @@
+.nut-audio-operate-group {
+  display: flex;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: 10px;
+
+  .nut-audio-operate {
+    .nut-audio-operate-item {
+      margin: 0 5px;
+    }
+  }
+}

+ 48 - 0
src/packages/__VUE/audiooperate/index.vue

@@ -0,0 +1,48 @@
+<template>
+  <!--配合进度条使用 播放时长、 兼容是否支持 、暂停、 开启-->
+  <div class="nut-audio-operate">
+    <div class="nut-audio-operate-item" @click="fastBack" v-if="type == 'back'"
+      ><nut-button type="primary" size="small" v-if="!customSlot">倒退</nut-button><slot></slot
+    ></div>
+    <div class="nut-audio-operate-item" @click="changeStatus" v-if="type == 'play'"
+      ><nut-button type="primary" size="small" v-if="!customSlot">{{
+        !audioData.playing ? '开始' : '暂停'
+      }}</nut-button>
+      <slot></slot
+    ></div>
+    <div class="nut-audio-operate-item" @click="forward" v-if="type == 'forward'"
+      ><nut-button type="primary" size="small" v-if="!customSlot">快进</nut-button><slot></slot
+    ></div>
+    <div class="nut-audio-operate-item" @click="handleMute" v-if="type == 'mute'"
+      ><nut-button :type="!audioData.hanMuted ? 'primary' : 'default'" size="small" v-if="!customSlot">静音</nut-button
+      ><slot></slot
+    ></div>
+  </div>
+</template>
+<script lang="ts">
+import { toRefs, ref, useSlots, onMounted, reactive, inject } from 'vue';
+import { createComponent } from '../../utils/create';
+const { componentName, create } = createComponent('audio-operate');
+
+export default create({
+  props: {
+    // 展示的形式   back 倒退   play 开始 or 暂停  forward 快进 mute 静音
+    type: {
+      type: Array,
+      default() {
+        return 'play';
+      }
+    }
+  },
+  components: {},
+  emits: ['click'],
+
+  setup(props, { emit }) {
+    const audio: any = inject('audioParent');
+    const parent: any = reactive(audio);
+    const customSlot = ref(useSlots().default);
+
+    return { ...toRefs(props), ...toRefs(parent), customSlot };
+  }
+});
+</script>

+ 6 - 1
src/packages/styles/font/config.json

@@ -68,7 +68,12 @@
         "horizontal",
         "date",
         "photograph",
-        "more-s"
+        "more-s",
+        "play-stop",
+        "play-start",
+        "play-double-back",
+        "play-double-forward",
+        "voice"
       ]
     },
     {

+ 118 - 3
src/packages/styles/font/demo_index.html

@@ -55,6 +55,36 @@
           <ul class="icon_lists dib-box">
           
             <li class="dib">
+              <span class="icon nutui-iconfont">&#xe608;</span>
+                <div class="name">voice</div>
+                <div class="code-name">&amp;#xe608;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon nutui-iconfont">&#xe604;</span>
+                <div class="name">play-stop</div>
+                <div class="code-name">&amp;#xe604;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon nutui-iconfont">&#xe605;</span>
+                <div class="name">play-start</div>
+                <div class="code-name">&amp;#xe605;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon nutui-iconfont">&#xe606;</span>
+                <div class="name">play-double-back</div>
+                <div class="code-name">&amp;#xe606;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon nutui-iconfont">&#xe607;</span>
+                <div class="name">play-double-forward</div>
+                <div class="code-name">&amp;#xe607;</div>
+              </li>
+          
+            <li class="dib">
               <span class="icon nutui-iconfont">&#xe603;</span>
                 <div class="name">dou-arrow-up</div>
                 <div class="code-name">&amp;#xe603;</div>
@@ -744,9 +774,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'nutui-iconfont';
-  src: url('iconfont.woff2?t=1642470100417') format('woff2'),
-       url('iconfont.woff?t=1642470100417') format('woff'),
-       url('iconfont.ttf?t=1642470100417') format('truetype');
+  src: url('iconfont.woff2?t=1644572435352') format('woff2'),
+       url('iconfont.woff?t=1644572435352') format('woff'),
+       url('iconfont.ttf?t=1644572435352') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -773,6 +803,51 @@
         <ul class="icon_lists dib-box">
           
           <li class="dib">
+            <span class="icon nutui-iconfont nut-icon-voice"></span>
+            <div class="name">
+              voice
+            </div>
+            <div class="code-name">.nut-icon-voice
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon nutui-iconfont nut-icon-play-stop"></span>
+            <div class="name">
+              play-stop
+            </div>
+            <div class="code-name">.nut-icon-play-stop
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon nutui-iconfont nut-icon-play-start"></span>
+            <div class="name">
+              play-start
+            </div>
+            <div class="code-name">.nut-icon-play-start
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon nutui-iconfont nut-icon-play-double-back"></span>
+            <div class="name">
+              play-double-back
+            </div>
+            <div class="code-name">.nut-icon-play-double-back
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon nutui-iconfont nut-icon-play-double-forward"></span>
+            <div class="name">
+              play-double-forward
+            </div>
+            <div class="code-name">.nut-icon-play-double-forward
+            </div>
+          </li>
+          
+          <li class="dib">
             <span class="icon nutui-iconfont nut-icon-dou-arrow-up"></span>
             <div class="name">
               dou-arrow-up
@@ -1809,6 +1884,46 @@
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#nut-icon-voice"></use>
+                </svg>
+                <div class="name">voice</div>
+                <div class="code-name">#nut-icon-voice</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#nut-icon-play-stop"></use>
+                </svg>
+                <div class="name">play-stop</div>
+                <div class="code-name">#nut-icon-play-stop</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#nut-icon-play-start"></use>
+                </svg>
+                <div class="name">play-start</div>
+                <div class="code-name">#nut-icon-play-start</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#nut-icon-play-double-back"></use>
+                </svg>
+                <div class="name">play-double-back</div>
+                <div class="code-name">#nut-icon-play-double-back</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#nut-icon-play-double-forward"></use>
+                </svg>
+                <div class="name">play-double-forward</div>
+                <div class="code-name">#nut-icon-play-double-forward</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#nut-icon-dou-arrow-up"></use>
                 </svg>
                 <div class="name">dou-arrow-up</div>

+ 23 - 3
src/packages/styles/font/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "nutui-iconfont"; /* Project id 2166874 */
-  src: url('iconfont.woff2?t=1642470100417') format('woff2'),
-       url('iconfont.woff?t=1642470100417') format('woff'),
-       url('iconfont.ttf?t=1642470100417') format('truetype');
+  src: url('iconfont.woff2?t=1644572435352') format('woff2'),
+       url('iconfont.woff?t=1644572435352') format('woff'),
+       url('iconfont.ttf?t=1644572435352') format('truetype');
 }
 
 .nutui-iconfont {
@@ -13,6 +13,26 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.nut-icon-voice:before {
+  content: "\e608";
+}
+
+.nut-icon-play-stop:before {
+  content: "\e604";
+}
+
+.nut-icon-play-start:before {
+  content: "\e605";
+}
+
+.nut-icon-play-double-back:before {
+  content: "\e606";
+}
+
+.nut-icon-play-double-forward:before {
+  content: "\e607";
+}
+
 .nut-icon-dou-arrow-up:before {
   content: "\e603";
 }

文件差异内容过多而无法显示
+ 25 - 24
src/packages/styles/font/iconfont.js


+ 35 - 0
src/packages/styles/font/iconfont.json

@@ -6,6 +6,41 @@
   "description": "nutui 3.0字体管理",
   "glyphs": [
     {
+      "icon_id": "27579944",
+      "name": "voice",
+      "font_class": "voice",
+      "unicode": "e608",
+      "unicode_decimal": 58888
+    },
+    {
+      "icon_id": "27574732",
+      "name": "play-stop",
+      "font_class": "play-stop",
+      "unicode": "e604",
+      "unicode_decimal": 58884
+    },
+    {
+      "icon_id": "27574733",
+      "name": "play-start",
+      "font_class": "play-start",
+      "unicode": "e605",
+      "unicode_decimal": 58885
+    },
+    {
+      "icon_id": "27574749",
+      "name": "play-double-back",
+      "font_class": "play-double-back",
+      "unicode": "e606",
+      "unicode_decimal": 58886
+    },
+    {
+      "icon_id": "27574750",
+      "name": "play-double-forward",
+      "font_class": "play-double-forward",
+      "unicode": "e607",
+      "unicode_decimal": 58887
+    },
+    {
       "icon_id": "27276580",
       "name": "dou-arrow-up",
       "font_class": "dou-arrow-up",

二进制
src/packages/styles/font/iconfont.ttf


二进制
src/packages/styles/font/iconfont.woff


二进制
src/packages/styles/font/iconfont.woff2