实现Web QQ音乐打开现有新标签页切换音乐

  • 若没有打开播放音乐标签页,则打开新标签页播放所选音乐
  • 如果已打开新标签页,则直接切换所选音乐

pageA.vue

<script setup lang="ts">
const tab2 = ref<any>(null);
const router = useRouter();

interface Track {
  id: number;
  name: string;
  url: string;
}
const tracks = reactive<Track[]>([
  {
    id: 1,
    name: "Sample Track 1",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
  },
  {
    id: 2,
    name: "Sample Track 2",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
  },
  {
    id: 3,
    name: "Sample Track 3",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3",
  },
  {
    id: 4,
    name: "Sample Track 4",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3",
  },
  {
    id: 5,
    name: "Sample Track 5",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3",
  },
  {
    id: 6,
    name: "Sample Track 6",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3",
  },
  {
    id: 7,
    name: "Sample Track 7",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-7.mp3",
  },
  {
    id: 8,
    name: "Sample Track 8",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3",
  },
  {
    id: 9,
    name: "Sample Track 9",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3",
  },
  {
    id: 10,
    name: "Sample Track 10",
    url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-10.mp3",
  },
]);
const openWindow = (track: Track) => {
  // 如果标签页 2 已经存在且未关闭
  if (tab2.value && !tab2.value.closed) {
    // 激活标签页 2
    tab2.value.focus();
    const channel = new BroadcastChannel("myChannel");
    channel.postMessage(JSON.stringify(track));
    channel.close();
  } else {
    const target = router.resolve({
      path: "communicationB",
      query: { isNew: 1, track: JSON.stringify(track) },
    });
    // 如果标签页 2 不存在或已经关闭,则打开新的标签页
    tab2.value = window.open(target.href, "_blank");
  }
};
</script>

<template>
  <div class="music-list">
    <h1>Music Playlist</h1>
    <ul>
      <li
        v-for="track in tracks"
        :key="track.id"
        class="track-item"
        @click="openWindow(track)"
      >
        <span>{{ track.name }}</span>
      </li>
    </ul>
  </div>
</template>

<style scoped lang="scss">
.music-list {
  max-width: 500px;
  padding: 20px;
  margin: 0 auto;
  background: linear-gradient(145deg, #f0f4ff, #cfd9ff);
  border-radius: 20px;
  box-shadow:
    5px 5px 15px rgb(0 0 0 / 10%),
    -5px -5px 15px rgb(255 255 255 / 70%);
}

h1 {
  margin-bottom: 20px;
  font-size: 24px;
  font-weight: bold;
  color: #333;
  text-align: center;
  letter-spacing: 1px;
}

ul {
  padding: 0;
  list-style-type: none;
}

.track-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px;
  margin-bottom: 15px;
  cursor: pointer;
  background: #fff;
  border-radius: 12px;
  box-shadow:
    3px 3px 8px rgb(0 0 0 / 10%),
    -3px -3px 8px rgb(255 255 255 / 70%);
  transition:
    transform 0.2s,
    box-shadow 0.2s;
}

.track-item:hover {
  box-shadow:
    8px 8px 20px rgb(0 0 0 / 15%),
    -8px -8px 20px rgb(255 255 255 / 70%);
  transform: translateY(-5px);
}

span {
  font-size: 18px;
  font-weight: 500;
  color: #333;
  letter-spacing: 0.5px;
}
</style>

pageB.vue

<template>
  <div class="music-list">
    <h1>Music Playlist</h1>
    <ul>
      <li v-for="track in tracks" :key="track.id" class="track-item">
        <button
          :class="{ active: currentTrack?.id === track.id }"
          @click="playMusic(track)"
        >
          {{ track.name }}
        </button>
      </li>
    </ul>

    <!-- 进度条 -->
    <div v-if="currentTrack?.url" class="progress-container relative">
      <span
        v-if="currentTrack.name"
        class="absolute left-50% transform-translate-x--50% bottom-20px"
      >
        {{ currentTrack?.name }}
      </span>
      <span>{{ formatTime(currentTime) }}</span>
      <progress :value="currentTime" :max="duration" />
      <span>{{ formatTime(duration) }}</span>
      <button class="!w-64px ml-10px" @click="togglePlay">
        {{ isPlaying ? "Pause" : "Play" }}
      </button>
    </div>

    <audio ref="audioPlayer" @timeupdate="updateProgress" @ended="onTrackEnd" />
  </div>
</template>

<script lang="ts">
import { ref, reactive, onMounted } from "vue";
const channel = ref<any>();

interface Track {
  id: number;
  name: string;
  url: string;
}

export default {
  setup() {
    const tracks = reactive<Track[]>([
      {
        id: 1,
        name: "Sample Track 1",
        url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
      },
      {
        id: 2,
        name: "Sample Track 2",
        url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
      },
      {
        id: 3,
        name: "Sample Track 3",
        url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3",
      },
    ]);

    const currentTrack = ref<Track>();
    const audioPlayer = ref<HTMLAudioElement | null>(null);
    const currentTime = ref(0); // 当前播放时间
    const duration = ref(0); // 当前音频时长
    const isPlaying = ref(false); // 播放状态

    const playMusic = (track: Track) => {
      if (audioPlayer.value) {
        currentTrack.value = track;
        audioPlayer.value.src = track.url;
        audioPlayer.value.play();
        isPlaying.value = true;

        // 每次播放新音乐时重置进度
        currentTime.value = 0;
        console.log(currentTime);
        duration.value = 0;
      }
    };

    const updateProgress = () => {
      if (audioPlayer.value) {
        currentTime.value = audioPlayer.value.currentTime; // 当前时间
        duration.value = audioPlayer.value.duration || 0; // 总时长
      }
    };

    const onTrackEnd = () => {
      isPlaying.value = false; // 播放结束时,暂停状态
      const index = tracks.findIndex((t) => t.id === currentTrack.value!.id);
      if (currentTrack.value!.url && index < tracks.length - 1) {
        const track = tracks[index + 1];
        playMusic(track); // 自动播放下一首
      } else {
        currentTrack.value = {
          id: -1,
          name: "",
          url: "",
        }; // 播放结束
      }
    };
    // 播放或暂停
    const togglePlay = () => {
      if (audioPlayer.value) {
        if (isPlaying.value) {
          audioPlayer.value.pause();
        } else {
          audioPlayer.value.play();
        }
        isPlaying.value = !isPlaying.value;
      }
    };
    const onPlay = () => {
      isPlaying.value = true;
      if (audioPlayer.value) {
        audioPlayer.value.play();
      }
    };
    const formatTime = (time: number): string => {
      const minutes = Math.floor(time / 60);
      const seconds = Math.floor(time % 60)
        .toString()
        .padStart(2, "0");
      return `${minutes}:${seconds}`;
    };

    onMounted(() => {
      if (audioPlayer.value) {
        audioPlayer.value.volume = 0.8; // 设置默认音量
      }
      channel.value = new BroadcastChannel("myChannel");
      channel.value.onmessage = (event: any) => {
        const data = JSON.parse(event.data);
        if (data.id !== currentTrack.value?.id) {
          playMusic(data);
        }
        onPlay();
      };
    });
    onMounted(() => {
      const route = useRoute();
      const router = useRouter();
      const { query } = route as any;
      // 判断是否是新打开的标签页
      if (query.isNew === "1") {
        console.log(query);
        // 清空地址栏参数
        router.replace({ query: {} });
        playMusic(JSON.parse(query.track));
      }
    });
    onBeforeUnmount(() => {
      channel.value.close();
    });

    return {
      tracks,
      currentTrack,
      currentTime,
      duration,
      playMusic,
      updateProgress,
      onTrackEnd,
      formatTime,
      audioPlayer,
      togglePlay,
      isPlaying,
    };
  },
};
</script>

<style scoped>
.music-list {
  max-width: 600px;
  padding: 20px;
  margin: 0 auto;
  background-color: #f4f4f4;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgb(0 0 0 / 10%);
}

h1 {
  color: #333;
  text-align: center;
}

ul {
  padding: 0;
  list-style-type: none;
}

.track-item {
  margin: 10px 0;
}

button {
  width: 100%;
  padding: 10px;
  font-size: 16px;
  color: white;
  cursor: pointer;
  background-color: #6200ea;
  border: none;
  border-radius: 4px;
  transition: background-color 0.3s;
}

button.active {
  background-color: #3700b3;
}

button:hover {
  background-color: #5c00d2;
}

.progress-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 20px;
}

progress {
  flex: 1;
  height: 6px;
  margin: 0 10px;
  appearance: none;
  background-color: #ddd;
}

progress::-webkit-progress-bar {
  background-color: #ddd;
}

progress::-webkit-progress-value {
  background-color: #6200ea;
}

progress::-moz-progress-bar {
  background-color: #6200ea;
}

span {
  font-size: 14px;
  color: #333;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值