天天看點

Vue開發曆程---音樂播放器

前言

淺淺記錄一下自己開發音樂播放器的曆程,鞏固自己的所學。同時也是深感基礎不牢,地動山搖。

一、audio标簽的使用

1、Audio 對象屬性

屬性 描述
audioTracks 傳回表示可用音頻軌道的 AudioTrackList 對象。
autoplay 設定或傳回是否在就緒(加載完成)後随即播放音頻。
buffered 傳回表示音頻已緩沖部分的 TimeRanges 對象。
controller 傳回表示音頻目前媒體控制器的 MediaController 對象。
controls 設定或傳回音頻是否應該顯示控件(比如播放/暫停等)。
crossOrigin 設定或傳回音頻的 CORS 設定。
currentSrc 傳回目前音頻的 URL。
currentTime 設定或傳回音頻中的目前播放位置(以秒計)。
defaultMuted 設定或傳回音頻預設是否靜音。
defaultPlaybackRate 設定或傳回音頻的預設播放速度。
duration 傳回音頻的長度(以秒計)。
ended 傳回音頻的播放是否已結束。
error 傳回表示音頻錯誤狀态的 MediaError 對象。
loop 設定或傳回音頻是否應在結束時再次播放。
mediaGroup 設定或傳回音頻所屬媒介組合的名稱。
muted 設定或傳回是否關閉聲音。
networkState 傳回音頻的目前網絡狀态。
paused 設定或傳回音頻是否暫停。
playbackRate 設定或傳回音頻播放的速度。
played 傳回表示音頻已播放部分的 TimeRanges 對象。
preload 設定或傳回音頻的 preload 屬性的值。
readyState 傳回音頻目前的就緒狀态。
seekable 傳回表示音頻可尋址部分的 TimeRanges 對象。
seeking 傳回使用者目前是否正在音頻中進行查找。
src 設定或傳回音頻的 src 屬性的值。
textTracks 傳回表示可用文本軌道的 TextTrackList 對象。
volume 設定或傳回音頻的音量。

2、對象方法

方法 描述
addTextTrack() 向音頻添加新的文本軌道。
canPlayType() 檢查浏覽器是否能夠播放指定的音頻類型。
fastSeek() 在音頻播放器中指定播放時間。
getStartDate() 傳回新的 Date 對象,表示目前時間線偏移量。
load() 重新加載音頻元素。
play() 開始播放音頻。
pause() 暫停目前播放的音頻。

二、效果

效果如下:
Vue開發曆程---音樂播放器

三、代碼

代碼如下:

MusicPlayer.vue

<template>
    <div class="music">
        <!-- 占位 -->
        <div class="m_hold">

        </div>
        <div class="m_img">
            <img :src="this.$parent.songNames[this.$parent.index].png" width="90px" :class="this.$parent.isRun">
        </div>
        <!-- 歌曲資訊 -->
        <div class="m_text">
            {{ this.$parent.songNames[this.$parent.index].name }}
            <div class="block" style="margin-top:5px">
                <el-slider :v-model="value1"></el-slider>
            </div>
        </div>
        <!-- 按鈕 -->
        <div class="m_btn">
            <a href="#" class="m_prev" @click="playLastSong()"></a>
            <a href="#" class="m_play" @click="changeState()" v-show="this.$parent.isShow"></a>
            <a href="#" class="m_pause" @click="changeState()" v-show="!this.$parent.isShow"></a>
            <a href="#" class="m_next" @click="playNextSong()"></a>
        </div>
        <!-- 折疊功能 -->
        <div class="m_close" @click="changeCloseState()">
            <a href=""></a>
        </div>

    </div>
</template>

<script>
export default {
    name: 'MusicPlayer',
    data() {
        return {
            songName: '',
            value1:0

        }
    },
    methods: {
        changeState() {

            this.$emit("play")
        },
        changeCloseState() {
            this.$emit("hello");
        },
        playNextSong() {
            this.$emit("nextSongs");
            this.songName = this.$parent.songNames[this.$parent.index].name
        },
        playLastSong() {
            this.$emit("lastSongs");
            this.songName = this.$parent.songNames[this.$parent.index].name
        }
    },
    watch:
    {
       

    }, mounted() {
        this.songName = this.$parent.songNames[this.$parent.index].name
    }

}
</script>

<style scoped>
/* 關于播放器的樣式 */
.music {
    width: 100%;
    height: 120px;
    background: black;
    /* 相對浏覽器定位 */
    position: absolute;
    left: 0px;
    bottom: 100px;
    border-bottom: 50px;
    /* 透明度 */
    opacity: 0.8;
    /* 陰影值 */
    box-shadow: 10px 15px 15px 1px black
}

.music .m_hold {
    float: left;
    width: 90px;
    height: 90px;
}

/* 調整音樂盒圖檔 */
.music .m_img {
    margin-top: 15px;
    margin-left: 10px;
    margin-right: 10px;
    /* 左浮動 */
    float: left;
    width: 90px;
    height: 90px;
    border-radius: 50%;
    overflow: hidden;

}

/* 修改文字 */
.music .m_text {
    /* 左浮動 */
    float: left;
    color: white;
    font-size: 20px;
    /* 字型加粗 */
    font-weight: bold;
    margin-top: 25px;
    margin-left: 20px;
    margin-bottom: 10px;
    width: 25%;

}

/* 使得所有a标簽一起移動 */
.music .m_btn {
    float: left;
    position: absolute;
    /* 絕對定位:防止歌曲名稱過長,擠出div */
    left: 40%;
}

/* 修改a标簽 */
.music .m_btn a {
    width: 32px;
    height: 32px;
    float: left;
    margin-top: 50px;
    margin-left: 20px;
    background: url(@/assets/player_bg.png);

}

.music .m_btn .m_prev {
    background-position: -69px 0px;
}

.music .m_btn .m_next {
    background-position: -150px 0px;
}

.music .m_btn .m_play {
    background-position: -107px -5px;
}

.music .m_btn .m_prev:hover {
    background-position: -69px -32px;
}

.music .m_btn .m_next:hover {
    background-position: -150px -32px;
}

.music .m_btn .m_play:hover {
    background-position: -107px -47px;
}

.music .m_btn .m_pause {
    background-position: -292px -94px;
}

.music .m_btn .m_pause:hover {
    background-position: -334px -94px;
}

/* 還有一個懸停 沒寫 */
/* 設定最右邊的關閉樣式 */
.music .m_close {
    float: right;
    background: white;
    cursor: pointer;
    width: 23px;
    height: 100px;
    margin-top: 10px;
    background: url(@/assets/player_bg.png);

}

/* 設定最右邊的關閉樣式 */
.music_hide {
    float: left;
    background: white;
    cursor: pointer;
    width: 23px;
    height: 100px;
    margin-top: 2px;
}

.go {
    animation: bounce-in 2s linear infinite;
}

.come {
    animation: none;
}

@keyframes bounce-in {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

.open-enter-active {
    animation: slide-in linear 0.5s;
}

.open-leave-active {
    animation: slide-in reverse linear 0.5s;
}

@keyframes slide-in {
    from {
        transform: translateX(-100%);
    }

    to {
        transform: translateX(0%);
    }
}
</style>      

HideMusic.vue

<template>
    <div class="music_hide" @click="changeCloseState()"><a href="#" class="m_open"></a></div>
</template>

<script>
export default {
    name:'HidePlayer',
    methods:{
        changeCloseState()
        {
            this.$emit("hello");
        }
    }
}
</script>

<style scoped>
.music_hide {
    float: left;
    background: url(@/assets/player_bg.png);
    cursor: pointer;
    width: 23px;
    height: 100px;
    margin-top: 10px;
    bottom: 100px;
    position: absolute;
    background-position-x: -45px;
}
</style>      

MyPlayer.vue

<template>
    <div>
        <transition name="open" mode="out-in">
            <component v-bind:is="view" @hello="changeSlideState" @play="changePlayState" @lastSongs="lastSongs"
                @nextSongs="nextSongs"></component>
        </transition>
        <audio class="m_mp3" id="m_mp3" :src="this.songNames[this.index].Url" autoplay loop>

        </audio>
    </div>

</template>

<script>
import HidePlayer from '@/part/HidePlayer'
import MusicPlayer from '@/part/MusicPlayer'
export default {
    name: 'MyPlayer',
    data() {
        return {
            view: MusicPlayer,
            isClose: false,
            isShow: true,
            isRun: 'come',
            index: 0,
            songNum: 2,
            currentTime: '0:00',
            duration: '0:00',
            songNames: [
                {
                    id: 1,
                    name: '張韶涵-篇章',
                    Url: require('@/assets/張韶涵-篇章.mp3'),
                    png: require('@/assets/篇章.png'),
                },
                {
                    id: 2,
                    name: '愛就一個字 抒情版',
                    Url: require('@/assets/愛就一個字 抒情版.mp3'),
                    png: require('@/assets/愛就一個字.png'),
                },
                {
                    id: 3,
                    name: '最偉大的作品-周傑倫',
                    Url: require('@/assets/最偉大的作品-周傑倫.mp3'),
                    png: require('@/assets/周傑倫.jpg'),
                },
                {
                    id: 4,
                    name: '等你下課 (with 楊瑞代)-周傑倫',
                    Url: require('@/assets/等你下課 (with 楊瑞代)-周傑倫.mp3'),
                    png: require('@/assets/等你下課.png'),
                },
                {
                    id: 5,
                    name: '告白氣球-周傑倫',
                    Url: require('@/assets/告白氣球-周傑倫.mp3'),
                    png: require('@/assets/告白氣球.png'),
                },
                {
                    id: 6,
                    name: '還在流浪-周傑倫',
                    Url: require('@/assets/還在流浪-周傑倫.mp3'),
                    png: require('@/assets/還在流浪.png'),
                },
            ]
        }
    },
    components: {
        HidePlayer,
        MusicPlayer
    },
    methods: {
        changeSlideState() {
            this.isClose = !this.isClose;
            if (this.isClose) {
                this.view = HidePlayer;
            } else {
                this.view = MusicPlayer;
            }
        },
        changePlayState() {
            if (!this.isShow) {
                this.isShow = true;
                this.isRun = "come";
                document.getElementById("m_mp3").pause();
            } else {
                this.isShow = false;
                this.isRun = "go";
                var my_mp3 = document.getElementById("m_mp3");
                my_mp3.play();


            }
        },
        nextSongs() {
            if (this.isShow) {
                this.isShow = false;
                this.isRun = "go";
            }
            this.index = (this.index + 1) % this.songNum;
        },
        lastSongs() {
            if (this.isShow) {
                this.isShow = false;
                this.isRun = "go";
            }
            if (this.index == 0) {
                this.index = this.songNum - 1;
            } else {
                this.index = this.index - 1;
            }

        }
    }, mounted() {
        this.songNum = this.songNames.length;
        
    }

}
</script>

<style scoped>
.open-enter-active {
    animation: slide-in linear 0.5s;
}

.open-leave-active {
    animation: slide-in reverse linear 0.5s;
}

@keyframes slide-in {
    from {
        transform: translateX(-100%);
    }

    to {
        transform: translateX(0%);
    }
}
</style>      

四、難點解析

1、過渡動畫的實作

Vue開發曆程---音樂播放器

參考了vue文檔過渡&動畫中多個元件的過渡(下面三份代碼)。​​vue文檔​​

<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>      
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})      
.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
  opacity: 0;
}      

是以分化出MusicPlayer.vue 和 HideMusic.vue,由此又産生了元件内通信的問題。

2、元件内通信

Vue開發曆程---音樂播放器

為什麼會産生元件内的通信?原因在于:MusicPlayer元件和HidePlayer元件,隻能有一個展示,但是在不展示的過程中,他的資料應該也是實時改變的。例如MusicPlayer元件上有播放按鈕,如果不采用元件通信,那麼MusicPlayer重新渲染的時候,播放按鈕會回到最初的設定,是不符合邏輯的。是以需要采用元件内通信的方式。實作的方式也比較簡單,子元件直接通路父元件的資料,子元件通過$emit調用父元件的方法,修改父元件的資料。

3、旋轉動畫的實作

Vue開發曆程---音樂播放器
.go {
    animation: bounce-in 2s linear infinite;
}

.come {
    animation: none;
}

@keyframes bounce-in {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}      
<div class="m_img">
   <img :src="this.$parent.songNames[this.$parent.index].png" width="90px" :class="this.$parent.isRun">
</div>      

總結