Vue 2.x折腾记 - (13) Nuxt.js写一个常规音频的播放组件,动态注入微信,新浪微博的js-sdk

简介: 只是一个常规的播放组件,需要考虑微信,微博这类环境的播放微信和微博,若没有用其官方的js-sdk初始化,没法播放。我的文章从来都不推崇copy,仅供参考学习..具体业务具体分析定制才是最合理的


前言


只是一个常规的播放组件,需要考虑微信,微博这类环境的播放


微信和微博,若没有用其官方的js-sdk初始化,没法播放。


我的文章从来都不推崇copy,仅供参考学习..具体业务具体分析定制才是最合理的


前置基础


  • vue && vuex
  • ES5+
  • Nuxt的基本用法


这篇文章的内容需基于上篇内容的,要用到一些设备信息


效果图


这是当前服务端版本的效果,因为还没上线,LOGO已经马赛克



实现思路


之前老的客户端实现思路


  • 在主入口实现一个单例,绑定到vue.prototype
  • 在音频组件的beforeMount创建script标签,引入对应js,然后用promise拿到成功加入head的状态
  • vuex来维护播放状态
  • 在对应的函数初始化音频的加载,之后就可以正常使用了


服务端的思路也差不多


考虑的东西多些,在之前客户端实现的基础上加以完善

用中间件这些来动态注入js-sdk


代码实现


客户端渲染实现的版本


版本1


全部耦合到组件内,虽然可以正常播放(包括微信和微博) 且不是单例模式,对于多音频页面,有毒


<template>
  <div class="play-voice-area">
    <div class="cover-player">
      <div
        :class="playState?'active':''"
        class="cover-pic">
        <img :src="coverUrl ? coverUrl:defaultAvatar">
        <i
          :class="playState? 'sx-mobile-icon-':'sx-mobile-bofang'"
          class="sx-mobile cover-icon"
          @click="playAudio" />
      </div>
    </div>
    <div class="sound-desrc">
      <p class="username">{{ userName }}的声兮</p>
      <p class="timeline">{{ currentPlayTime }}/{{ voiceTime }}</p>
    </div>
  </div>
</template>
<script>
export default {
    props: {
        userName: {
            type: String,
            default: 'Super Hero'
        },
        duration: {
            type: [String, Number],
            default: ''
        },
        autoplay: {
            type: [Boolean, String],
            default: false
        },
        sourceUrl: {
            type: String,
            default: ''
        },
        coverpic: {
            type: String,
            default: ''
        }
    },
    data() {
        return {
            defaultAvatar: require('@/assets/share/yourAppIcon@2x.png'), // 默认头像
            audioElm: '', // 音频播放器 DOM
            soundCurrentStopTime: '', // 当前声音暂停的时间戳
            playState: false, // 播放状态的图标控制
            timeStepState: '', // 时间迭代
            voicePlayMessage: '', // 音频资源的状况
            currentPlayTime: '00:00', // 当前播放的时间,默认为0
            cacheCurrentTime: 0 // 缓存播放时间
        };
    },
    computed: {
        coverUrl() {
            if (!this.coverpic) {
                return this.defaultAvatar;
            }
            return this.coverpic;
        },
        voiceTime() {
            if (this.duration) {
                return this.second2time(Number(this.duration));
            }
        }
    },
    watch: {
        sourceUrl(newVal, oldVal) {
            if (newVal) {
                this.playAudio();
            }
        }
    },
    created() {
        this.$store.commit('OPEN_LOADING');
    },
    beforeMount() {
        // 初始化音频播放器
        this.initAudioElm();
    },
    mounted() {
        // 检测微博微信平台
        this.checkWeiBo_WeiChat();
        this.audioElm.addEventListener('stalled', this.stalled);
        this.audioElm.addEventListener('loadstart', this.loadstart);
        this.audioElm.addEventListener('loadeddata', this.loadeddata);
        this.audioElm.addEventListener('canplay', this.canplay);
        this.audioElm.addEventListener('ended', this.ended);
        this.audioElm.addEventListener('pause', this.pause);
        this.audioElm.addEventListener('timeupdate', this.timeupdate);
        this.audioElm.addEventListener('error', this.error);
        this.audioElm.addEventListener('abort', this.abort);
    },
    beforeDestroy() {
        this.audioElm.removeEventListener('loadstart', this.loadstart);
        this.audioElm.removeEventListener('stalled', this.stalled);
        this.audioElm.removeEventListener('canplay', this.canplay);
        this.audioElm.removeEventListener('timeupdate', this.timeupdate);
        this.audioElm.removeEventListener('pause', this.pause);
        this.audioElm.removeEventListener('error', this.error);
        this.audioElm.removeEventListener('ended', this.ended);
    },
    methods: {
        initAudioElm() {
            let audio = new Audio();
            audio.autobuffer = true; // 自动缓存
            audio.preload = 'metadata';
            audio.src = this.sourceUrl;
            audio.load();
            this.audioElm = audio;
        },
        checkWeiBo_WeiChat() {
            let ua = navigator.userAgent.toLowerCase(); // 获取判断用的对象
            const script = document.createElement('script');
            if (/micromessenger/.test(ua)) {
                // 返回一个独立的promise
                script.src = 'https://res.wx.qq.com/open/js/jweixin-1.2.0.js';
                new Promise((resolve, reject) => {
                    let done = false;
                    script.onload = script.onreadystatechange = () => {
                        if (
                            !done &&
                                (!script.readyState ||
                                    script.readyState === 'loaded' ||
                                    script.readyState === 'complete')
                        ) {
                            done = true;
                            // 避免内存泄漏
                            script.onload = script.onreadystatechange = null;
                            resolve(script);
                        }
                    };
                    script.onerror = reject;
                    document
                        .getElementsByTagName('head')[0]
                        .appendChild(script);
                }).then(res => {
                    this.initWeixinSource();
                });
            }
            if (/WeiBo|weibo/i.test(ua)) {
                script.src =
                        'https://tjs.sjs.sinajs.cn/open/thirdpart/js/jsapi/mobile.js';
                new Promise((resolve, reject) => {
                    let done = false;
                    script.onload = script.onreadystatechange = () => {
                        if (
                            !done &&
                                (!script.readyState ||
                                    script.readyState === 'loaded' ||
                                    script.readyState === 'complete')
                        ) {
                            done = true;
                            // 避免内存泄漏
                            script.onload = script.onreadystatechange = null;
                            resolve(script);
                        }
                    };
                    script.onerror = reject;
                    document
                        .getElementsByTagName('head')[0]
                        .appendChild(script);
                }).then(res => {
                    this.initWeiboSource();
                });
            }
        },
        canplay() {
            this.$store.commit('CLOSE_LOADING');
        },
        initWeixinSource() {
            wx.config({
                // 配置信息, 即使不正确也能使用 wx.ready
                debug: false,
                appId: '',
                timestamp: 1,
                nonceStr: '',
                signature: '',
                jsApiList: []
            });
            wx.ready(() => {
                let st = setTimeout(() => {
                    clearTimeout(st);
                    this.audioElm.load();
                }, 50);
            });
        },
        initWeiboSource() {
            window.WeiboJS.init(
                {
                    appkey: '3779229073',
                    debug: false,
                    timestamp: 1429258653,
                    noncestr: '8505b6ef40',
                    scope: [
                        'getNetworkType',
                        'networkTypeChanged',
                        'getBrowserInfo',
                        'checkAvailability',
                        'setBrowserTitle',
                        'openMenu',
                        'setMenuItems',
                        'menuItemSelected',
                        'setSharingContent',
                        'openImage',
                        'scanQRCode',
                        'pickImage',
                        'getLocation',
                        'pickContact',
                        'apiFromTheFuture'
                    ]
                },
                ret => {
                    this.audioElm.load();
                }
            );
        },
        playAudio() {
            // 播放暂停音频
            if (this.audioElm.readyState > 2) {
                // 当资源可以播放的时候
                if (this.audioElm.paused) {
                    this.cacheCurrentTime === 0
                        ? (this.audioElm.currentTime = 0)
                        : (this.audioElm.currentTime = this.cacheCurrentTime);
                    this.playState = true;
                    this.audioElm.play();
                } else {
                    this.audioElm.pause();
                }
            }
        },
        second2time(currentTime) {
            // 秒数化为分钟
            let min = Math.floor(currentTime / 60); // 向下取整分钟
            let second = Math.floor(currentTime % 60); // 取模得到剩余秒数
            if (min < 10) {
                min = '0' + min;
            }
            if (second < 10) {
                second = '0' + second;
            }
            return `${min}:${second}`;
        },
        stalled() {
            // 资源需要缓存的时候暂停
            this.audioElm.pause();
            // 缓存加载待播的时候,若是当前播放时间已经走动则触发播放
            if (this.audioElm.currentTime !== 0) {
                // 判断当前播放的时间是否到达结束,否则则继续播放
                if (this.audioElm.currentTime !== this.audioElm.duration) {
                    this.playAudio();
                } else {
                    this.ended();
                }
            }
        },
        timeupdate() {
            if (
                this.audioElm.readyState > 2 &&
                    this.audioElm.currentTime > 0.2
            ) {
                this.cacheCurrentTime = this.audioElm.currentTime;
                this.currentPlayTime = this.second2time(
                    Number(this.audioElm.currentTime)
                );
                if (
                    this.audioElm.ended ||
                        this.audioElm.currentTime === this.audioElm.duration
                ) {
                    this.ended();
                }
            }
        },
        ended() {
            this.audioElm.pause();
            // 清除缓存的时间
            this.cacheCurrentTime = 0;
            this.voicePlayMessage = '';
        },
        pause() {
            // 当音频/视频已暂停时
            this.playState = false;
        },
        error(err) {
            // 当在音频/视频加载期间发生错误时
            this.audioElm.pause();
            this.voicePlayMessage = '音频加载资源错误!';
            console.log('我报错了:' + err);
        },
        abort() {
            this.audioElm.pause();
        }
    }
};
</script>
<style lang="scss" scoped>
    .play-voice-area {
    display: flex;
    align-items: center;
    flex-direction: column;
    justify-content: center;
}
.cover-player {
    position: relative;
    display: flex;
    align-items: center;
    flex-direction: column;
    flex-shrink: 0;
    justify-content: center;
    .cover-pic {
    display: block;
    overflow: hidden;
    width: 446px;
    height: 446px;
    transition: animation 0.28s;
    border: 15px solid hsla(0, 0%, 100%, 0.1);
    border-radius: 223px;
    img {
        display: inline-block;
        width: 446px;
        height: 446px;
    }
    &.active {
        animation: rotation 8s 0.1s linear infinite;
    }
    }
    .cover-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
    font-size: 100px;
    }
    a,
    button,
    input,
    textarea {
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    }
}
.sound-desrc {
    display: flex;
    overflow: hidden;
    align-items: center;
    flex-direction: column;
    justify-content: center;
    padding: 40px 0 0 0;
    .username {
    min-width: 243px;
    height: 38px;
    margin: 22px 0;
    text-align: center;
    letter-spacing: 0px;
    text-overflow: ellipsis;
    color: #c4c9e2;
    font-size: 36px;
    font-weight: normal;
    font-weight: 700;
    font-stretch: normal;
    line-height: 38px;
    }
    .timeline {
    width: 243px;
    height: 38px;
    text-align: center;
    color: #c4c9e2;
    font-size: 36px;
    font-weight: normal;
    font-stretch: normal;
    line-height: 38px;
    line-height: 38px;
    }
}
@keyframes rotation {
    from {
    -webkit-transform: rotate(0deg);
    }
    to {
    -webkit-transform: rotate(360deg);
    }
}
</style>


版本2


这个版本考虑了多音频播放,所以在主入口直接单例挂载了一个播放器

其次考虑音频的切换播放,所以必须依赖Vuex来共享状态


main.js-主入口


// 创建全局播放器
const music = new Audio();
Vue.prototype.player = music;


  • 状态


状态很简单,就一些基础信息,module的方式,state通过getters暴露


export default {
    state: {
        index: '',
        playState: false,
        curTime: '00:00'
    },
    mutations: {
        CURRENT_PLAY: (state, index) => {
            state.index = index;
        },
        CURRENT_TIME: (state, time) => {
            state.curTime = time;
        },
        SetPlayState(state, status) {
            state.playState = status;
        }
    }
};


播放组件组件


<template>
  <div
    @click="playstop"
    class="icon-wrap"
    :class="iconSize"
    :style="{color:iconColor}">
    <i
      class="sx-mobile"
      :class="playState ? iconShow.stop : iconShow.play" />
  </div>
</template>
<script>
export default {
    props: {
        iconShow: {
            type: Object,
            default: function() {
                return {
                    play: 'sx-mobile-bofang',
                    stop: 'sx-mobile-icon-'
                };
            }
        },
        iconSize: {
            type: String,
            default: 'normal'
        },
        iconColor: {
            type: String,
            default: '#FFF'
        },
        playState: {
            type: Boolean,
            default: false
        },
        sourceUrl: {
            type: String,
            default: ''
        },
        mode: {
            type: String,
            default: 'self'
        }
    },
    created() {
        // 检测微博微信平台
        this.checkWeiBo_WeiChat();
        console.log(this.sourceUrl);
    },
    mounted() {
        this.player.addEventListener('end', this.voiceEnd);
    },
    methods: {
        checkWeiBo_WeiChat() {
            let ua = navigator.userAgent.toLowerCase(); // 获取判断用的对象
            const script = document.createElement('script');
            if (/micromessenger/.test(ua)) {
                // 返回一个独立的promise
                script.src = 'https://res.wx.qq.com/open/js/jweixin-1.2.0.js';
                new Promise((resolve, reject) => {
                    let done = false;
                    script.onload = script.onreadystatechange = () => {
                        if (
                            !done &&
                              (!script.readyState ||
                                  script.readyState === 'loaded' ||
                                  script.readyState === 'complete')
                        ) {
                            done = true;
                            // 避免内存泄漏
                            script.onload = script.onreadystatechange = null;
                            resolve(script);
                        }
                    };
                    script.onerror = reject;
                    document
                        .getElementsByTagName('head')[0]
                        .appendChild(script);
                }).then(res => {
                    this.initWeixinSource();
                });
            }
            if (/WeiBo|weibo/i.test(ua)) {
                script.src =
                      'https://tjs.sjs.sinajs.cn/open/thirdpart/js/jsapi/mobile.js';
                new Promise((resolve, reject) => {
                    let done = false;
                    script.onload = script.onreadystatechange = () => {
                        if (
                            !done &&
                              (!script.readyState ||
                                  script.readyState === 'loaded' ||
                                  script.readyState === 'complete')
                        ) {
                            done = true;
                            // 避免内存泄漏
                            script.onload = script.onreadystatechange = null;
                            resolve(script);
                        }
                    };
                    script.onerror = reject;
                    document
                        .getElementsByTagName('head')[0]
                        .appendChild(script);
                }).then(res => {
                    this.initWeiboSource();
                });
            }
        },
        initWeixinSource() {
            wx.config({
                // 配置信息, 即使不正确也能使用 wx.ready
                debug: false,
                appId: '',
                timestamp: 1,
                nonceStr: '',
                signature: '',
                jsApiList: []
            });
            wx.ready(() => {
                let st = setTimeout(() => {
                    clearTimeout(st);
                    this.player.load();
                }, 50);
            });
        },
        initWeiboSource() {
            window.WeiboJS.init(
                {
                    appkey: '3779229073',
                    debug: false,
                    timestamp: 1429258653,
                    noncestr: '8505b6ef40',
                    scope: [
                        'getNetworkType',
                        'networkTypeChanged',
                        'getBrowserInfo',
                        'checkAvailability',
                        'setBrowserTitle',
                        'openMenu',
                        'setMenuItems',
                        'menuItemSelected',
                        'setSharingContent',
                        'openImage',
                        'scanQRCode',
                        'pickImage',
                        'getLocation',
                        'pickContact',
                        'apiFromTheFuture'
                    ]
                },
                ret => {
                    this.player.load();
                }
            );
        },
        second2time(currentTime) {
            /* 秒数化为分钟 */
            let min = parseInt(currentTime / 60, 10); // 向下取整分钟
            let second = parseInt(currentTime % 60, 10); // 取模得到剩余秒数
            if (min < 10) {
                min = '0' + min;
            }
            if (second < 10) {
                second = '0' + second;
            }
            return `${min}:${second}`;
        },
        playstop() {
            if (this.mode === 'self') {
                this.player.paused ? this.playVoice() : this.pauseVoice();
            } else {
                if (this.$store.getters.vindex === this.index) {
                    this.player.paused ? this.playVoice() : this.pauseVoice();
                } else {
                    this.player.src = this.sourceUrl;
                    this.player.play();
                    if (!this.player.paused) {
                        this.$store.commit('SetPlayState', true);
                        this.$store.commit('CURRENT_PLAY', this.index);
                    }
                }
            }
        },
        playVoice() {
            if (this.player.src !== '') {
                this.player.play();
                if (!this.player.paused) {
                    this.$store.commit('SetPlayState', true);
                    this.$store.commit('CURRENT_PLAY', this.index);
                    if (this.mode === 'self') {
                        this.playState = true;
                    }
                }
            } else {
                this.player.src = this.sourceUrl;
                this.playVoice();
            }
        },
        pauseVoice() {
            this.player.pause();
            this.$store.commit('SetPlayState', false);
            if (this.mode === 'self') {
                this.playState = false;
            }
        },
        voiceEnd() {
            if (this.mode === 'self') {
                this.$emit('update:playState', false);
            }
        }
    },
};
</script>
<style lang="scss" scoped>
  .icon-wrap {
    &.small {
      font-size: 16px;
    }
    &.normal {
      font-size: 32px;
    }
    &.large {
      font-size: 64px;
    }
    &.huge {
      font-size: 96px;
    }
    &.big {
      font-size: 128px;
    }
    i {
      font-size: inherit;
    }
  }
</style>


服务端渲染实现的版本(Nuxt)


audio_browser_inject_head.js件(middleware目录)


// 这里给标签加了spec标记,是为了防止多次访问同一个页面的时候,
// 无限的插入新增的js
// 这次就不再nuxt.config.js引入中间件了.因为不是面向全局,直接在对应的页面引入即可
export default context => {
  const { env } = context.deviceType;
  const HeadScript = context.app.head.script;
  if (env === "wechat") {
    if (!HeadScript[HeadScript.length - 1].spec) {
      HeadScript.push({
        src: "https://res.wx.qq.com/open/js/jweixin-1.3.2.js",
        type: "text/javascript",
        charset: "utf-8",
        spec: true,
      });
    }
  }
  if (env === "weibo") {
    if (!HeadScript[HeadScript.length - 1].spec) {
      HeadScript.push({
        src: "http://tjs.sjs.sinajs.cn/open/thirdpart/js/jsapi/mobile.js",
        type: "text/javascript",
        charset: "utf-8",
        spec: true,
      });
    }
  }
};


单例播放器(plugins目录)


  • plugins/player.js


import Vue from "vue";
export default ({ app, store }) => {
  let player = new Audio();
  player.preload = "auto";
  // 把单例的播放器提交到vuex去管控
  store.commit("voice/SetPlayer", player);
};


  • nuxt.config.js


因为audio对象只有客户端才有,所以不能服务端初始化 设置ssr:false就代表在客户端的时候才注入,默认不写ssrtrue


module.exports = {
  plugins: [ { src: "~plugins/player.js", ssr: false }]
};


Vuex(store目录)


  • 默认的index.js是根状态,其他再改目录下的js文件均默认当做vuexmodule


// index.js
import Vuex from "vuex";
export const state = () => ({
  deviceType: {},
});
export const mutations = {
  SetDeviceType(state, payload) {
    state.deviceType = payload;
  },
};
export const getters = {
  deviceType(state) {
    return state.deviceType;
  },
  player(state) {
    return state.voice.player;
  },
  playState(state) {
    return state.voice.playState;
  },
  playUrl(state) {
    return state.voice.playUrl;
  },
  playIndex(state) {
    return state.voice.playIndex;
  },
  playTime(state) {
    return state.voice.playTime;
  },
  voiceTotalTime(state) {
    return state.voice.voiceTotalTime;
  },
};
// voice.js
import Vuex from "Vuex";
export const state = () => ({
  player: "", // 播放器
  playState: false, // 当前播放的状态
  playUrl: "", // 播放的链接
  playIndex: 0, // 当前播放的索引
  playTime: "00:00", // 当前的播放时间
  voiceTotalTime: "00:00", // 曲目总时长
});
export const mutations = {
  SetPlayer(state, payload) {
    state.player = payload;
  },
  SetPlayState(state, payload) {
    state.playState = payload;
  },
  SetPlayUrl(state, payload) {
    state.playUrl = payload;
    state.player.src = payload;
  },
  SetPlayIndex(state, payload) {
    state.playIndex = payload;
  },
  SetPlayTime(state, payload) {
    state.playTime = payload;
  },
  SetVoiceTotalTime(state, payload) {
    state.voiceTotalTime = payload;
  },
  ResetVoice(state) {
    state.playState = false;
    state.playUrl = "";
    state.playTime = "00:00";
    state.voiceTotalTime = "00:00";
  },
};


播放器组件


  • VoicePlayer.vue


播放状态均由vuex来管理,这样对于多音频或者跨组件控制播放非常有帮助


<template>
  <div class="player"
       :class="$store.getters.playState ? 'animation-roate':''"
       :style="{background:`url(${CoverImg}) center center no-repeat`,backgroundSize: 'cover'}">
    <div class="icon-wrap">
      <img :src="playstate? StopIcon:PlayIcon"
           alt="播放器操作按钮"
           @click="changePlayState(playstate)">
    </div>
  </div>
</template>
<script>
  const CoverImg = require('./images/cover@2x.png');
  const PlayIcon = require('./images/play@2x.png');
  const StopIcon = require('./images/stop@2x.png');
  export default {
    data() {
      return {
        CoverImg,
        PlayIcon,
        StopIcon,
      }
    },
    props: {
      playstate: {
        type: Boolean,
        default: false
      },
      playurl: {
        type: String,
        default: 'http://www.ytmp3.cn/down/51013.mp3'
      }
    },
    mounted() {
      this.$store.getters.player.addEventListener('loadedmetadata', () => {
        // 缓存播放总时长
        this.$store.commit('voice/SetVoiceTotalTime', this.second2time(this.$store.getters.player.duration));
      })
      this.$store.getters.player.addEventListener('stalled', () => {
        // 重置播放状态
        this.$store.commit('voice/ResetVoice');
      })
      this.$store.getters.player.addEventListener('abort', () => {
        // 重置播放状态
        this.$store.commit('voice/ResetVoice');
      })
      this.$store.getters.player.addEventListener('play', () => {
        this.$store.commit('voice/SetPlayState', true);
      })
      this.$store.getters.player.addEventListener('pause', () => {
        this.$store.commit('voice/SetPlayState', false);
      })
      this.$store.getters.player.addEventListener('timeupdate', () => {
        this.$store.commit('voice/SetPlayTime', this.second2time(this.$store.getters.player.currentTime));
      })
      this.$store.getters.player.addEventListener('ended', () => {
        this.$store.commit('voice/ResetVoice');
      })
    },
    beforeDestroy() {
      this.$store.getters.player.removeEventListener('loadedmetadata', () => {
        this.$store.commit('voice/SetVoiceTotalTime', this.second2time(this.$store.getters.player.duration));
      })
      this.$store.getters.player.removeEventListener('stalled', () => {
        this.$store.commit('voice/ResetVoice');
      })
      this.$store.getters.player.removeEventListener('abort', () => {
        this.$store.commit('voice/ResetVoice');
      })
      this.$store.getters.player.removeEventListener('play', () => {
        this.$store.commit('voice/SetPlayState', true);
      })
      this.$store.getters.player.removeEventListener('pause', () => {
        this.$store.commit('voice/SetPlayState', false);
      })
      this.$store.getters.player.removeEventListener('timeupdate', () => {
        this.$store.commit('voice/SetPlayTime', this.second2time(this.$store.getters.player.currentTime));
        console.log(this.$store.getters.player.currentTime)
      })
      this.$store.getters.player.removeEventListener('ended', () => {
        this.$store.commit('voice/ResetVoice');
      })
    },
    methods: {
      changePlayState(playstate) {
        // 设置播放源
        if (!this.$store.getters.playUrl) {
          this.$store.commit('voice/SetPlayUrl', this.playurl)
        }
        // 设置播放状态
        if (playstate) {
          this.$store.getters.player.pause();
        } else {
          this.$store.getters.player.play();
        }
        playstate = !playstate;
      },
      initWeixinSource() {
        wx.config({
          // 配置信息, 即使不正确也能使用 wx.ready
          debug: false,
          appId: '',
          timestamp: 1,
          nonceStr: '',
          signature: '',
          jsApiList: []
        });
        wx.ready(() => {
          let st = setTimeout(() => {
            clearTimeout(st);
            this.player.load();
          }, 50);
        });
      },
      initWeiboSource() {
        window.WeiboJS.init(
          {
            appkey: '3779229073',
            debug: false,
            timestamp: 1429258653,
            noncestr: '8505b6ef40',
            scope: [
              'getNetworkType',
              'networkTypeChanged',
              'getBrowserInfo',
              'checkAvailability',
              'setBrowserTitle',
              'openMenu',
              'setMenuItems',
              'menuItemSelected',
              'setSharingContent',
              'openImage',
              'scanQRCode',
              'pickImage',
              'getLocation',
              'pickContact',
              'apiFromTheFuture'
            ]
          },
          ret => {
            this.audioElm.load();
          }
        );
      },
      second2time(currentTime) {
        // 秒数化为分钟
        let min = Math.floor(currentTime / 60); // 向下取整分钟
        let second = Math.floor(currentTime % 60); // 取模得到剩余秒数
        if (min < 10) {
          min = '0' + min;
        }
        if (second < 10) {
          second = '0' + second;
        }
        return `${min}:${second}`;
      },
    }
  }
</script>
<style lang="scss" scoped>
  .player {
    height: 100%;
    width: 100%;
    border-radius: 100%;
    position: relative;
    .icon-wrap {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      img {
        display: block;
        height: 94px;
        width: 94px;
      }
    }
  }
  @keyframes fade-rotate {
    from {
      opacity: 0.8;
      transform: rotate(0) scale(1);
    }
    to {
      opacity: 1;
      transform: rotate(360deg) scale(1.1);
    }
  }
  .animation-roate {
    transform: translate3d(0, 0, 0);
    animation: fade-rotate 18s ease-in-out infinite alternate;
  }
</style>
目录
相关文章
|
9月前
|
存储 JavaScript API
百宝箱开放平台 ✖️ Node.js SDK
开发者可以通过安装 Node.js SDK 的方式将百宝箱的 OpenAPI 集成到自有系统中,从而在外部系统中发起智能体对话。
489 0
百宝箱开放平台 ✖️ Node.js SDK
|
JavaScript 前端开发 开发者
VUE 开发——Node.js学习(一)
VUE 开发——Node.js学习(一)
356 2
|
JavaScript 前端开发 开发者
哇塞!Vue.js 与 Web Components 携手,掀起前端组件复用风暴,震撼你的开发世界!
【8月更文挑战第30天】这段内容介绍了Vue.js和Web Components在前端开发中的优势及二者结合的可能性。Vue.js提供高效简洁的组件化开发,单个组件包含模板、脚本和样式,方便构建复杂用户界面。Web Components作为新兴技术标准,利用自定义元素、Shadow DOM等技术创建封装性强的自定义HTML元素,实现跨框架复用。结合二者,不仅增强了Web Components的逻辑和交互功能,还实现了Vue.js组件在不同框架中的复用,提高了开发效率和可维护性。未来前端开发中,这种结合将大有可为。
682 0
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
708 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
JSON 前端开发 API
使用微信JS-SDK调用发票接口的完整开发指南
本文介绍了如何使用微信JS-SDK的`chooseInvoiceTitle`接口来调用微信的发票功能。通过微信发票接口,用户可以选择开具个人或单位发票,并获取相关发票信息,如抬头、税号、公司地址等。在文中,详细描述了JS-SDK的初始化、发票接口的调用方式,并提供了完整的代码示例。文章还介绍了如何处理返回的发票信息,帮助开发者快速集成微信发票功能。
955 2
|
JavaScript 前端开发 开发工具
【Azure Developer】使用JavaScript通过SDK进行monitor-query的client认证报错问题
AADSTS90002: Tenant 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant.
212 1
|
JavaScript 前端开发 API
Vue学习笔记3:对比纯JavaScript和Vue实现数据更新的实时视图显示
Vue学习笔记3:对比纯JavaScript和Vue实现数据更新的实时视图显示
|
移动开发 安全 API
微信H5支付--微信JS-SDK支付--点金计划
本文详细介绍了微信H5支付和JS-SDK支付的原理、配置和开发流程,涵盖了H5支付在移动端浏览器外唤起微信支付的细节,以及JS-SDK支付在微信内置浏览器中完成支付的相关注意事项。文章还针对微信支付常见问题,提供了解决方案和代码示例。最后,文章深入解析了微信支付点金计划,包括商家小票的自定义开发、API接口以及支付成功后的页面展示逻辑,为开发者提供了完整的开发参考。
1731 0
微信H5支付--微信JS-SDK支付--点金计划
|
JavaScript 前端开发 API
探索Vue.js 3的组合式API:一种更灵活的组件状态管理方式
【10月更文挑战第5天】探索Vue.js 3的组合式API:一种更灵活的组件状态管理方式
|
前端开发 JavaScript 开发者
Express.js与前端框架的集成:React、Vue和Angular的示例与技巧
本文介绍了如何将简洁灵活的Node.js后端框架Express.js与三大流行前端框架——React、Vue及Angular进行集成,以提升开发效率与代码可维护性。文中提供了详细的示例代码和实用技巧,展示了如何利用Express.js处理路由和静态文件服务,同时在React、Vue和Angular中构建用户界面,帮助开发者快速掌握前后端分离的开发方法,实现高效、灵活的Web应用构建。
682 3