master
yujialong 2 months ago
commit 62bd512f8b
  1. 9
      .gitignore
  2. 2
      .npmrc
  3. 10
      .umirc.ts
  4. 26
      package.json
  5. 8955
      pnpm-lock.yaml
  6. BIN
      src/assets/cyclePlay.png
  7. BIN
      src/assets/list.png
  8. BIN
      src/assets/listPlay.png
  9. BIN
      src/assets/music.png
  10. BIN
      src/assets/musicListNext.png
  11. BIN
      src/assets/next.png
  12. BIN
      src/assets/prev.png
  13. BIN
      src/assets/regularPlay.png
  14. BIN
      src/assets/sequentialPlay.png
  15. 139
      src/components/Popup/index.less
  16. 101
      src/components/Popup/index.tsx
  17. 104
      src/components/rgup/index.less
  18. 104
      src/components/rgup/index.tsx
  19. 10
      src/layouts/index.less
  20. 7
      src/layouts/index.tsx
  21. 9
      src/pages/docs.tsx
  22. 139
      src/pages/index.less
  23. 481
      src/pages/index.tsx
  24. 468
      src/pages/listen.tsx
  25. 3
      tsconfig.json
  26. 1
      typings.d.ts

9
.gitignore vendored

@ -0,0 +1,9 @@
/node_modules
/.env.local
/.umirc.local.ts
/config/config.local.ts
/src/.umi
/src/.umi-production
/src/.umi-test
/dist
.swc

@ -0,0 +1,2 @@
registry=https://registry.npmmirror.com/

@ -0,0 +1,10 @@
import { defineConfig } from "umi";
export default defineConfig({
routes: [
{ path: "/", component: "index" },
{ path: "/docs", component: "docs" },
{ path: "/listen", component: "listen" },
],
npmClient: 'pnpm',
});

@ -0,0 +1,26 @@
{
"private": true,
"author": "",
"scripts": {
"dev": "umi dev",
"build": "umi build",
"postinstall": "umi setup",
"setup": "umi setup",
"start": "npm run dev"
},
"dependencies": {
"antd": "^5.20.6",
"axios": "^1.7.7",
"hls.js": "^1.5.15",
"react-audio-player": "^0.17.0",
"react-player": "^2.16.0",
"umi": "^4.3.20",
"video.js": "^8.17.4",
"videojs-contrib-hls.js": "^3.2.0"
},
"devDependencies": {
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"typescript": "^5.0.3"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,139 @@
@keyframes slideInFromBottom {
from {
bottom: -100%;
opacity: 0;
}
to {
bottom: 0;
opacity: 1;
}
}
.popup_overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.popup_half_screen {
position: fixed;
bottom: 0; // 移动到屏幕底部
left: 0;
width: 100%;
height: 500px; // 占据屏幕高度的一半
background-color:white;
z-index: 1000;
overflow-y: auto;
// background-color: black;
border-radius: 1rem 1rem 0 0;
animation: slideInFromBottom .5s ease;
.popup_content {
width: 100%;
height: auto;
.header {
width: 40px;
height: 4px;
margin: 14px auto;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
}
.content {
width: 100%;
height: 400px;
margin: auto;
overflow: scroll;
.container {
height: auto;
display: flex;
flex-direction: column;
.item {
width: 100%;
display: flex;
margin: 2px auto 3px auto;
.content {
width: calc(100% - 4rem - 30px - 1rem);
height: 88px;
margin: auto 0 auto 2rem;
display: flex;
flex-direction: column;
.title {
width: auto;
height: 1.5rem;
font-weight: 600;
font-size: 1rem;
line-height: 1.4375rem;
margin: .5rem auto .25rem 0;
}
.subtitle {
width: calc(100vw - 1rem - 1.875rem - 4rem);
height: 1.25rem;
font-size: .875rem;
line-height: 1.25rem;
color: rgba(0, 0, 0, .6);
margin: 0 auto .25rem 0;
white-space: nowrap;//不支持换行
overflow: hidden;//隐藏多出部分文字
text-overflow: ellipsis;//用省略号代替多出部分文字
line-clamp: 1;
}
.duration {
width: 3.75rem;
height: .875rem;
font-size: .875rem;
line-height: .875rem;
color: rgba(0, 0, 0, .3);
margin: .25rem auto auto .375rem;
}
.duration::before {
content: '';
display: inline-block;
margin-right: 6px;
width: .75rem;
height: .75rem;
background-image: url(../../assets/music.png);
background-repeat: no-repeat;
background-size: contain;
}
}
.next {
width: 1.875rem;
height: 1.875rem;
background-image: url(../../assets/musicListNext.png);
background-repeat: no-repeat;
background-size: contain;
margin: auto 2rem auto auto;
}
}
}
}
.footer {
width: 100%;
height: 48px;
margin: auto auto 0 auto;
text-align: center;
justify-content: center;
line-height: 48px;
font-weight: 600;
font-size: 16px;
}
}
}

@ -0,0 +1,101 @@
import { useEffect, useState } from 'react';
import './index.less'
export default function PopupHalfScreen(
{
visible,
onClose,
list,
index,
setIndex
}: any) {
//管理关闭动画状态
const [isAnimationOpen, setisAnimationOpen] = useState(false)
useEffect(() => {
}, [])
if (!visible) {
return null;
}
const handleOverlayClick = () => {
// 点击蒙层时关闭弹窗
setisAnimationOpen(true)
setTimeout(() =>{
setisAnimationOpen(false)
onClose();
}, 500)
};
const formatSecondsToTime = (seconds: number) => {
// 将秒数转换为整数秒和毫秒
const totalSeconds = Math.floor(seconds);
// 分钟数
const minutes = Math.floor(totalSeconds / 60);
// 剩余秒数
const remainingSeconds = totalSeconds % 60;
// 补零操作
const formattedMinutes = minutes.toString().padStart(2, '0');
const formattedSeconds = remainingSeconds.toString().padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`;
}
return (
<>
<div
className='popup_overlay'
onClick={handleOverlayClick}
/>
<div
className='popup_half_screen'
onClick={onClose}
style={
!isAnimationOpen ? {} : {
bottom: '-100%',
transition: 'bottom 0.5s ease',
}
}
>
<div className='popup_content' onClick={(e) => e.stopPropagation()}>
{/* 弹窗内容 */}
<div className='header'></div>
<div className='content'>
<div className='container'>
{
list.map((item: any, list_index: number) => (
<div
key={list_index}
className='item'
onClick={setIndex.bind(null, list_index)}
style={index === list_index ? {backgroundColor: 'rgba(0, 0, 0, .03)'} : {}}
>
<div className='content'>
<div className='title'>{item.ResTitle}</div>
<div className='subtitle'>{item.ResSubtitle}</div>
<div className='duration'>{formatSecondsToTime(item.ResDuration)}</div>
</div>
<div
className='next'
style={index === list_index ? {backgroundImage: 'url("https://oss.jm-kid.com/wx_5colorflower/assets/music_note.gif")'} : {}}
></div>
</div>
))
}
</div>
</div>
<div
className='footer'
onClick={handleOverlayClick}
></div>
</div>
</div>
</>
);
}

@ -0,0 +1,104 @@
@keyframes slideInFromBottom {
from {
bottom: -100%;
opacity: 0;
}
to {
bottom: 0;
opacity: 1;
}
}
.popup_overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.rgup_half_screen {
position: fixed;
bottom: 0; // 移动到屏幕底部
left: 0;
width: 100%;
height: 322px; // 占据屏幕高度的一半
background-color:white;
z-index: 1000;
overflow-y: auto;
// background-color: black;
border-radius: 1rem 1rem 0 0;
animation: slideInFromBottom .5s ease;
.popup_content {
width: 100%;
height: auto;
.header {
width: 40px;
height: 4px;
margin: 14px auto;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
}
.content {
width: 100%;
height: 224px;
margin: auto;
overflow: scroll;
.title {
width: auto;
height: 24px;
margin: 0 auto 8px 16px;
font-weight: 600;
font-size: 16px;
}
.context {
width: 100%;
height: auto;
display: flex;
flex-direction: column;
.item {
width: 100%;
height: 48px;
display: flex;
flex-direction: row;
.text {
width: auto;
height: 24px;
margin: auto auto auto 16px;
color: rgba(0, 0, 0, 0.6);
}
.icon {
width: 24px;
height: 24px;
margin: auto 16px auto auto;
background-color: #000;
}
}
}
}
.footer {
width: 100%;
height: 48px;
margin: auto auto 0 auto;
text-align: center;
justify-content: center;
line-height: 48px;
font-weight: 600;
font-size: 16px;
}
}
}

@ -0,0 +1,104 @@
import { useEffect, useState } from 'react';
import './index.less'
const textList = [
{
text: '不限制',
mode: 2,
},
{
text: '10分钟后停止播放',
mode: 3,
},
{
text: '20分钟后停止播放',
mode: 4,
},
{
text: '30分钟后停止播放',
mode: 5,
},
]
export default function RgupHalfScreen(
{
visible,
onClose,
type,
setType,
}: any) {
//管理关闭动画状态
const [isAnimationOpen, setisAnimationOpen] = useState(false)
useEffect(() => {
}, [])
if (!visible) {
return null;
}
const handleOverlayClick = () => {
// 点击蒙层时关闭弹窗
setisAnimationOpen(true)
setTimeout(() =>{
setisAnimationOpen(false)
onClose();
}, 500)
};
return (
<>
<div
className='popup_overlay'
onClick={handleOverlayClick}
/>
<div
className='rgup_half_screen'
onClick={onClose}
style={
!isAnimationOpen ? {} : {
bottom: '-100%',
transition: 'bottom 0.5s ease',
}
}
>
<div className='popup_content' onClick={(e) => e.stopPropagation()}>
{/* 弹窗内容 */}
<div className='header'></div>
<div className='content'>
<div className='title'></div>
<div className='context'>
{
textList.map((item: any, index: number)=>(
<div
className='item'
key={index}
onClick={setType.bind(null, item.mode)}
>
<div
className='text'
style={
type === item.mode ? {fontWeight: '600', color: '#3ba366'} : {}
}
>
{item.text}
</div>
{
type === item.mode && <div className='icon'></div>
}
</div>
))
}
</div>
</div>
<div
className='footer'
onClick={handleOverlayClick}
></div>
</div>
</div>
</>
);
}

@ -0,0 +1,10 @@
// .navs {
// ul {
// padding: 0;
// list-style: none;
// display: flex;
// }
// li {
// margin-right: 1em;
// }
// }

@ -0,0 +1,7 @@
import { Outlet } from 'umi';
export default function Layout() {
return (
<Outlet />
);
}

@ -0,0 +1,9 @@
const DocsPage = () => {
return (
<div>
<p>This is umi docs.</p>
</div>
);
};
export default DocsPage;

@ -0,0 +1,139 @@
.audioItfc_bg {
width: 100%;
height: calc(100vh - 1rem - env(safe-area-inset-bottom));
display: flex;
flex-direction: column;
.audio_disc {
width: 15rem;
height: 15rem;
border-radius: 7.5rem;
// background-image: url(https://oss.jm-kid.com/wx_5colorflower/《屁》.png);
background-color: #000;
background-repeat: no-repeat;
background-size: cover;
margin: 5rem auto 0 auto;
border: .3125rem solid #fff;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.5);
.audio_disc_bearing {
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 4rem;
height: 4rem;
border-radius: 2rem;
background: #ffffff;
}
}
.audio_progress_bar {
width: 100%;
height: auto;
margin: auto auto calc(2rem + env(safe-area-inset-bottom)) auto;
display: flex;
flex-direction: column;
gap: .5rem;
.audio_title {
font-size: 1.125rem;
font-weight: 600;
color: #000;
text-align: center;
margin: 2rem auto 1rem 0;
}
.audio_discription {
font-size: .875rem;
color: rgba(0, 0, 0, 0.4);
margin: .5rem auto 0 0;
}
.audio_tips {
width: auto;
height: 1rem;
margin: .5rem auto auto 0;
display: flex;
flex-direction: row;
gap: .5rem;
.audio_tip {
width: auto;
height: 1rem;
border-radius: .375rem;
background: #3BA366;
display: flex;
padding: .0625rem .5625rem;
.audio_tip_text {
width: auto;
font-size: .625rem;
color: #ffffff;
text-align: center;
margin: auto;
}
}
}
.audio_progress_time_bar {
width: 100%;
height: 1rem;
display: flex;
flex-direction: row;
margin: .5rem auto 1rem auto;
.audio_progress_time {
font-size: .75rem;
color: #3BA366;
width: 1.875rem;
height: 100%;
}
}
.audio_progress {
margin: .5rem 0;
width: 100%;
}
.audio_operate {
height: 4.125rem;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
gap: 1rem;
.audio_operate_btn0 {
width: 3rem;
height: 3rem;
background-repeat: no-repeat;
background-size: cover;
margin: auto 0;
}
.audio_operate_btn1 {
width: 4rem;
height: 4rem;
background-repeat: no-repeat;
background-size: cover;
}
}
}
.drawer_contanier {
width: 100%;
height: 500px;
display: flex;
flex-direction: column;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg); /* 顺时针旋转360度 */
}
}

@ -0,0 +1,481 @@
import React, { useEffect, useRef, useState } from 'react'
import './index.less'
import { useLocation } from 'umi'
import axios from 'axios'
import * as HLS from 'hls.js';
import { ConfigProvider, Slider } from 'antd';
import PopupHalfScreen from '@/components/Popup';
import RgupHalfScreen from '@/components/rgup';
export default function HomePage() {
const location = useLocation()
const videoRef = useRef<any>(null);
const [testMusic, settestMusic] = useState("https://alihls.jm-kid.com/6b46fb4068b471ee803f6723b78e0102/fabee8a31fff0477f788beee1ac8901c-ld-encrypt-stream.m3u8")
const [MusicDesc, setMusicDesc] = useState<any>({})
const [currentTime, setcurrentTime] = useState(0)
const [durantionTime, setdurantionTime] = useState(230)
const [isPlay, setisPlay] = useState(false)
//检查是否为第一次播放,每次切换音频时需要重置
const [isFirst, setisFirst] = useState(true)
//音频列表
const [musicList, setmusicList] = useState<any[]>([])
//控制列表抽屉显示隐藏
const [isShowList, setisShowList] = useState<boolean>(false)
//记录列表序号index
const [listIndex, setlistIndex] = useState(0)
//记录音频播放模式
//0 列表循环
//1 单曲循环
//2 定时播放
const [playMode, setplayMode] = useState<number>(0)
//控制定时播放弹窗
const [isShowRg, setisShowRg] = useState(false)
const [timerId, setTimerId] = useState<null | ReturnType<typeof setInterval>>(null);
const [seconds, setSeconds] = useState(0);
const [selectedDuration, setSelectedDuration] = useState(600); // 默认选择600秒
//记录prdtId
const [reprdtId, setreprdtId] = useState('')
const formatSecondsToTime = (seconds: number) => {
// 将秒数转换为整数秒和毫秒
const totalSeconds = Math.floor(seconds);
// 分钟数
const minutes = Math.floor(totalSeconds / 60);
// 剩余秒数
const remainingSeconds = totalSeconds % 60;
// 补零操作
const formattedMinutes = minutes.toString().padStart(2, '0');
const formattedSeconds = remainingSeconds.toString().padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`;
}
// const { prdtId, resId } = useParams()
useEffect(() => {
console.log('location=>', location, window.location.href)
const url = window.location.href
// 正则表达式来匹配prdtId和resId的值
const regex = /\?prdtId=(\d+)&resId=(\d+)/;
const match = url.match(regex);
let prdtId: any;
let resId: any;
if (match) {
prdtId = match[1]; // 获取第一个捕获组的值
resId = match[2]; // 获取第二个捕获组的值
setreprdtId(prdtId)
console.log(`prdtId: ${prdtId}, resId: ${resId}`);
} else {
console.log('没有找到匹配项');
}
//
// prdtId => 4500
// resId => 36208
//请求音频列表
axios.post('https://interapi.jm-kid.com/api/audio/product/detail', {
timestamp: Date.now(),
prdt_id: prdtId ? prdtId : 5653,
}).then((res:any) => {
console.log('产品音频列表获取成功=>', res.data)
for(let i = 0; i < res.data.data.Resources.length; i++){
if(res.data.data.Resources[i].ResId === resId){
setlistIndex(i)
}
}
setmusicList(res.data.data.Resources)
axios.post('https://interapi.jm-kid.com/api/audio/resource', {
timestamp: Date.now(),
prdt_id: prdtId ? prdtId :5653,
res_id: resId ? resId : 44889 ,
}).then((res: any) => {
console.log('产品资源获取成功=>', res.data)
//判断播放方式
if(res.data.data.Resource.PlayType === 'url'){
const mDesc = {
PrdtTitle: res.data.data.Resource.ResTitle,
PrdtCover: res.data.data.Resource.ResCover,
}
setMusicDesc(mDesc)
settestMusic(res.data.data.Resource.PlayUrl)
setdurantionTime(res.data.data.Resource.ResDuration)
}else{
//app request for video resource
axios.post('https://api.jimeikid.com/jiyoumei/system/admin/employee/login', {
"password": "WxBmGKupbl30+v4hfQC8aQ==",
"userName": "VqwgIY4g7s9DanyyqsKP4g==",
}).then((token_res: any) => {
console.log('app token=>', token_res.data.data.token)
axios.get('https://api.jimeikid.com/jiyoumei/product/app/message/send/history/getPlayInfo',{
params: {
token: res.data.data.Resource.ResPwd,
videoId: res.data.data.Resource.ResVcode
},
headers: {
authorization: token_res.data.data.token
}
}).then((rres: any) => {
console.log('视频资源请求成功=>', rres.data)
const mDesc = {
PrdtTitle: res.data.data.Resource.ResTitle,
PrdtCover: res.data.data.Resource.ResCover,
}
setMusicDesc(mDesc)
console.log('视频资源=>',rres.data.data.body.playInfoList.playInfo[0].playURL)
settestMusic(rres.data.data.body.playInfoList.playInfo[0].playURL)
setdurantionTime(rres.data.data.body.playInfoList.playInfo[0].duration)
})
})
}
})
})
}, [])
const startTimer = () => {
if (timerId !== null) return; // 如果已经有计时器在运行,则返回
let id = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
if (seconds >= selectedDuration - 1) {
videoRef.current = null;
clearInterval(id);
setTimerId(null);
}
}, 1000);
setTimerId(id);
};
const stopTimer = () => {
if (timerId !== null) {
clearInterval(timerId);
setTimerId(null);
setSeconds(0); // 重置秒数
}
};
useEffect(() => {
return () => {
if (timerId !== null) {
clearInterval(timerId);
}
};
}, [timerId]); // 只有当 timerId 发生变化时才执行
useEffect(()=> {
if(playMode > 2){
if(selectedDuration !== (playMode - 2) * 600){
stopTimer()
setSelectedDuration((playMode - 2) * 600)
startTimer()
}
}else if(playMode === 2){
stopTimer()
}
}, [playMode])
useEffect(()=>{
if(currentTime === durantionTime - 1){
setcurrentTime(0)
}
const audio = videoRef.current;
if(isPlay){
if(currentTime !== 0){
if(isFirst){
console.log('第一次播放')
const hls = new HLS.default(); // 使用 HLS.js
console.log('加载HLS流地址=>', testMusic)
hls.loadSource(testMusic); // 加载 HLS 流地址
hls.attachMedia(audio); // 关联 media 元素
hls.on(HLS.Events.MANIFEST_PARSED, function() {
audio.currentTime = currentTime
audio.play()
setisFirst(false)
});
}else{
audio.play()
console.log('继续播放')
}
}else{
console.log('从头播放')
const hls = new HLS.default(); // 使用 HLS.js
console.log('加载HLS流地址=>', testMusic)
hls.loadSource(testMusic); // 加载 HLS 流地址
hls.attachMedia(audio); // 关联 media 元素
hls.on(HLS.Events.MANIFEST_PARSED, function() {
audio.play(); // 开始播放
});
}
}else{
audio.pause()
}
audio.addEventListener('timeupdate', () => {
setcurrentTime(audio.currentTime)
})
audio.addEventListener('ended', () => {
setisPlay(false)
if(playMode === 0){
if(listIndex < musicList.length - 1){
setlistIndex(listIndex + 1)
}else{
setlistIndex(0)
}
}else if(playMode === 1){
}else if(playMode === 2){
setplayMode(0)
}
audio.currentTime = 0
})
audio.addEventListener('error', (res: any) => {
console.log('error', res)
})
audio.addEventListener('pause', () => {
setcurrentTime(audio.currentTime)
setisPlay(false)
})
audio.addEventListener('play', () => {
setisPlay(true)
})
}, [isPlay])
useEffect(()=>{
if(listIndex !== -1 && musicList.length > 0){
console.log('切换音频', listIndex)
setisPlay(false)
axios.post('https://interapi.jm-kid.com/api/audio/resource', {
timestamp: Date.now(),
prdt_id: reprdtId ? reprdtId : 5653,
res_id: musicList[listIndex].ResId,
}).then((res: any) => {
console.log('产品资源获取成功=>', res.data)
//判断播放方式
if(res.data.data.Resource.PlayType === 'url'){
const mDesc = {
PrdtTitle: res.data.data.Resource.ResTitle,
PrdtCover: res.data.data.Resource.ResCover,
}
setMusicDesc(mDesc)
settestMusic(res.data.data.Resource.PlayUrl)
setdurantionTime(res.data.data.Resource.ResDuration)
setisFirst(true)
setcurrentTime(0)
setisPlay(true)
}else{
//app request for video resource
axios.post('https://api.jimeikid.com/jiyoumei/system/admin/employee/login', {
"password": "WxBmGKupbl30+v4hfQC8aQ==",
"userName": "VqwgIY4g7s9DanyyqsKP4g==",
}).then((token_res: any) => {
console.log('app token=>', token_res.data.data.token)
axios.get('https://api.jimeikid.com/jiyoumei/product/app/message/send/history/getPlayInfo',{
params: {
token: res.data.data.Resource.ResPwd,
videoId: res.data.data.Resource.ResVcode
},
headers: {
authorization: token_res.data.data.token
}
}).then((rres: any) => {
console.log('视频资源请求成功=>', rres.data)
const mDesc = {
PrdtTitle: res.data.data.Resource.ResTitle,
PrdtCover: res.data.data.Resource.ResCover,
}
setMusicDesc(mDesc)
console.log('视频资源=>',rres.data.data.body.playInfoList.playInfo[0].playURL)
settestMusic(rres.data.data.body.playInfoList.playInfo[0].playURL)
setdurantionTime(rres.data.data.body.playInfoList.playInfo[0].duration)
setisFirst(true)
setcurrentTime(0)
setisPlay(true)
})
})
}
})
}
}, [listIndex])
useEffect(()=>{
if(playMode > 1){
setisShowRg(true)
}
}, [playMode])
const handleClickList = () => {
setisShowList(true)
}
return (
<div className='audioItfc_bg'>
<div
className='audio_disc'
style={isPlay ? {animation: 'spin 10s linear infinite', backgroundImage: `url(${MusicDesc.PrdtCover})`} : {backgroundImage: `url(${MusicDesc.PrdtCover})`}}
>
<div className='audio_disc_bearing'></div>
</div>
<div className='audio_progress_bar'>
<div className='audio_title'>{MusicDesc.PrdtTitle}</div>
<ConfigProvider
theme={{
components: {
Slider: {
handleColor: '#3ba366',
handleActiveColor: '#3ba366',
railBg: 'rgba(59, 163, 102, 0.4)',
railHoverBg: 'rgba(59, 163, 102, 0.4)',
trackBg: '#3ba366',
trackHoverBg: '#3ba366',
}
}
}}
>
<Slider
step={1}
tooltip={{
open: false,
}}
min={0}
max={durantionTime}
value={currentTime}
onChange={(currentTime)=>{
console.log('拖拽中', currentTime)
setcurrentTime(currentTime)
}}
onChangeComplete={(currentTime)=>{
console.log('拖拽结束', currentTime)
const audio = videoRef.current
audio.currentTime = currentTime
}}
/>
</ConfigProvider>
<audio ref={videoRef}></audio>
<div className='audio_progress_time_bar'>
<div
className='audio_progress_time'
style={{marginRight: 'auto'}}
>
{formatSecondsToTime(currentTime)}
</div>
<div
className='audio_progress_time'
style={{marginLeft: 'auto'}}
>
{formatSecondsToTime(durantionTime)}
</div>
</div>
<div className='audio_operate'>
<img
className='audio_operate_btn0'
src={playMode === 0 ? 'https://oss.jm-kid.com/wx_5colorflower/assets/listPlay.png' : playMode === 1 ? 'https://oss.jm-kid.com/wx_5colorflower/assets/cyclePlay.png' : 'https://oss.jm-kid.com/wx_5colorflower/assets/regularPlay.png'}
onClick={() => {
if(playMode === 0){
setplayMode(1)
}else if(playMode === 1){
setplayMode(2)
}else{
setplayMode(0)
}
}}
></img>
<img
className='audio_operate_btn0'
src='https://oss.jm-kid.com/wx_5colorflower/assets/prev.png'
onClick={() => {
if(listIndex === 0){
setlistIndex(musicList.length - 1)
}else{
setlistIndex(listIndex - 1)
}
}}
></img>
<img
className='audio_operate_btn1'
src={isPlay ? 'https://oss.jm-kid.com/wx_5colorflower/pauseMusic.png' : 'https://oss.jm-kid.com/wx_5colorflower/playMusic.png'}
onClick={() => setisPlay(!isPlay)}
></img>
<img
className='audio_operate_btn0'
src='https://oss.jm-kid.com/wx_5colorflower/assets/next.png'
onClick={() => {
if(listIndex < musicList.length - 1){
setlistIndex(listIndex + 1)
}else{
setlistIndex(0)
}
}}
></img>
<img
className='audio_operate_btn0'
src='https://oss.jm-kid.com/wx_5colorflower/assets/list.png'
onClick={handleClickList}
></img>
</div>
</div>
<PopupHalfScreen
visible={isShowList}
onClose={() => setisShowList(false)}
list={musicList}
index={listIndex}
setIndex={setlistIndex}
></PopupHalfScreen>
<RgupHalfScreen
visible={isShowRg}
onClose={() => setisShowRg(false)}
type={playMode}
setType={setplayMode}
></RgupHalfScreen>
</div>
)
}

@ -0,0 +1,468 @@
import React, { useEffect, useRef, useState } from 'react'
import './index.less'
import { useLocation } from 'umi'
import axios from 'axios'
import * as HLS from 'hls.js';
import { ConfigProvider, Slider } from 'antd';
import PopupHalfScreen from '@/components/Popup';
import RgupHalfScreen from '@/components/rgup';
export default function HomePage() {
const location = useLocation()
const videoRef = useRef<any>(null);
const [testMusic, settestMusic] = useState("https://alihls.jm-kid.com/6b46fb4068b471ee803f6723b78e0102/fabee8a31fff0477f788beee1ac8901c-ld-encrypt-stream.m3u8")
const [MusicDesc, setMusicDesc] = useState<any>({})
const [currentTime, setcurrentTime] = useState(0)
const [durantionTime, setdurantionTime] = useState(230)
const [isPlay, setisPlay] = useState(false)
//检查是否为第一次播放,每次切换音频时需要重置
const [isFirst, setisFirst] = useState(true)
//音频列表
const [musicList, setmusicList] = useState<any[]>([])
//控制列表抽屉显示隐藏
const [isShowList, setisShowList] = useState<boolean>(false)
//记录列表序号index
const [listIndex, setlistIndex] = useState(0)
//记录音频播放模式
//0 列表循环
//1 单曲循环
//2 定时播放
const [playMode, setplayMode] = useState<number>(0)
//控制定时播放弹窗
const [isShowRg, setisShowRg] = useState(false)
const [timerId, setTimerId] = useState<null | ReturnType<typeof setInterval>>(null);
const [seconds, setSeconds] = useState(0);
const [selectedDuration, setSelectedDuration] = useState(600); // 默认选择600秒
//记录prdtId
const [reprdtId, setreprdtId] = useState('')
const formatSecondsToTime = (seconds: number) => {
// 将秒数转换为整数秒和毫秒
const totalSeconds = Math.floor(seconds);
// 分钟数
const minutes = Math.floor(totalSeconds / 60);
// 剩余秒数
const remainingSeconds = totalSeconds % 60;
// 补零操作
const formattedMinutes = minutes.toString().padStart(2, '0');
const formattedSeconds = remainingSeconds.toString().padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`;
}
// const { prdtId, resId } = useParams()
useEffect(() => {
console.log('location=>', location, window.location.href)
const url = window.location.href
// 正则表达式来匹配prdtId和resId的值
const regex = /\?prdtId=(\d+)/;
const match = url.match(regex);
let prdtId: any;
if (match) {
prdtId = match[1]; // 获取第一个捕获组的值
setreprdtId(prdtId)
console.log(`prdtId: ${prdtId}`);
} else {
console.log('没有找到匹配项');
}
//
// prdtId => 4500
// resId => 36208
//请求音频列表
axios.post('https:// interapi.jm-kid.com/api/product/resource', {
timestamp: Date.now(),
prdt_id: prdtId ? prdtId : 5113,
res_type: '03',
}).then((res:any) => {
console.log('产品音频列表获取成功=>', res.data)
setmusicList(res.data.data.StoryResource)
console.log('PlayType=>', res.data.data.StoryResource[0].PlayType)
//判断播放方式
if(res.data.data.StoryResource[0].PlayType === 'url'){
console.log('url播放方式')
const mDesc = {
PrdtTitle: res.data.data.StoryResource[0].ResTitle,
PrdtCover: res.data.data.StoryResource[0].ResCover,
}
setMusicDesc(mDesc)
settestMusic(res.data.data.StoryResource[0].PlayUrl)
setdurantionTime(res.data.data.StoryResource[0].ResDuration)
}else{
console.log('app播放方式')
//app request for video resource
axios.post('https://api.jimeikid.com/jiyoumei/system/admin/employee/login', {
"password": "WxBmGKupbl30+v4hfQC8aQ==",
"userName": "VqwgIY4g7s9DanyyqsKP4g==",
}).then((token_res: any) => {
console.log('app token=>', token_res.data.data.token)
axios.get('https://api.jimeikid.com/jiyoumei/product/app/message/send/history/getPlayInfo',{
params: {
token: res.data.data.StoryResource[0].ResPwd,
videoId: res.data.data.StoryResource[0].ResVcode
},
headers: {
authorization: token_res.data.data.token
}
}).then((rres: any) => {
console.log('视频资源请求成功=>', rres.data)
const mDesc = {
PrdtTitle: res.data.data.StoryResource[0].ResTitle,
PrdtCover: res.data.data.StoryResource[0].ResCover,
}
setMusicDesc(mDesc)
console.log('视频资源=>',rres.data.data.body.playInfoList.playInfo[0].playURL)
settestMusic(rres.data.data.body.playInfoList.playInfo[0].playURL)
setdurantionTime(rres.data.data.body.playInfoList.playInfo[0].duration)
})
})
}
})
}, [])
const startTimer = () => {
if (timerId !== null) return; // 如果已经有计时器在运行,则返回
let id = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
if (seconds >= selectedDuration - 1) {
videoRef.current = null;
clearInterval(id);
setTimerId(null);
}
}, 1000);
setTimerId(id);
};
const stopTimer = () => {
if (timerId !== null) {
clearInterval(timerId);
setTimerId(null);
setSeconds(0); // 重置秒数
}
};
useEffect(() => {
return () => {
if (timerId !== null) {
clearInterval(timerId);
}
};
}, [timerId]); // 只有当 timerId 发生变化时才执行
useEffect(()=> {
if(playMode > 2){
if(selectedDuration !== (playMode - 2) * 600){
stopTimer()
setSelectedDuration((playMode - 2) * 600)
startTimer()
}
}else if(playMode === 2){
stopTimer()
}
}, [playMode])
useEffect(()=>{
if(currentTime === durantionTime - 1){
setcurrentTime(0)
}
const audio = videoRef.current;
if(isPlay){
if(currentTime !== 0){
if(isFirst){
console.log('第一次播放')
const hls = new HLS.default(); // 使用 HLS.js
console.log('加载HLS流地址=>', testMusic)
hls.loadSource(testMusic); // 加载 HLS 流地址
hls.attachMedia(audio); // 关联 media 元素
hls.on(HLS.Events.MANIFEST_PARSED, function() {
audio.currentTime = currentTime
audio.play()
setisFirst(false)
});
}else{
audio.play()
console.log('继续播放')
}
}else{
console.log('从头播放')
const hls = new HLS.default(); // 使用 HLS.js
console.log('加载HLS流地址=>', testMusic)
hls.loadSource(testMusic); // 加载 HLS 流地址
hls.attachMedia(audio); // 关联 media 元素
hls.on(HLS.Events.MANIFEST_PARSED, function() {
audio.play(); // 开始播放
});
}
}else{
audio.pause()
}
audio.addEventListener('timeupdate', () => {
setcurrentTime(audio.currentTime)
})
audio.addEventListener('ended', () => {
console.log('音频播放结束')
setisPlay(false)
if(playMode === 0){
if(listIndex < musicList.length - 1){
console.log('下一首,更改listIndex', listIndex)
setlistIndex(listIndex + 1)
}else{
setlistIndex(0)
}
}else if(playMode === 1){
}else if(playMode === 2){
setplayMode(0)
}
audio.currentTime = 0
})
audio.addEventListener('error', (res: any) => {
console.log('error', res)
})
audio.addEventListener('pause', () => {
setcurrentTime(audio.currentTime)
setisPlay(false)
})
audio.addEventListener('play', () => {
setisPlay(true)
})
}, [isPlay])
useEffect(()=>{
if(listIndex !== -1 && musicList.length > 0){
console.log('切换音频', listIndex)
setisPlay(false)
//请求音频列表
axios.post('https:// interapi.jm-kid.com/api/product/resource', {
timestamp: Date.now(),
prdt_id: reprdtId ? reprdtId : 5113,
res_type: '03',
}).then((res:any) => {
console.log('产品音频列表获取成功=>', res.data)
//判断播放方式
if(res.data.data.StoryResource[listIndex].PlayType === 'url'){
const mDesc = {
PrdtTitle: res.data.data.StoryResource[listIndex].ResTitle,
PrdtCover: res.data.data.StoryResource[listIndex].ResCover,
}
setMusicDesc(mDesc)
settestMusic(res.data.data.StoryResource[listIndex].PlayUrl)
setdurantionTime(res.data.data.StoryResource[listIndex].ResDuration)
setisFirst(true)
setcurrentTime(0)
setisPlay(true)
}else{
//app request for video resource
axios.post('https://api.jimeikid.com/jiyoumei/system/admin/employee/login', {
"password": "WxBmGKupbl30+v4hfQC8aQ==",
"userName": "VqwgIY4g7s9DanyyqsKP4g==",
}).then((token_res: any) => {
console.log('app token=>', token_res.data.data.token)
axios.get('https://api.jimeikid.com/jiyoumei/product/app/message/send/history/getPlayInfo',{
params: {
token: res.data.data.StoryResource[listIndex].ResPwd,
videoId: res.data.data.StoryResource[listIndex].ResVcode
},
headers: {
authorization: token_res.data.data.token
}
}).then((rres: any) => {
console.log('视频资源请求成功=>', rres.data)
const mDesc = {
PrdtTitle: res.data.data.StoryResource[listIndex].ResTitle,
PrdtCover: res.data.data.StoryResource[listIndex].ResCover,
}
setMusicDesc(mDesc)
console.log('视频资源=>',rres.data.data.body.playInfoList.playInfo[0].playURL)
settestMusic(rres.data.data.body.playInfoList.playInfo[0].playURL)
setdurantionTime(rres.data.data.body.playInfoList.playInfo[0].duration)
setisFirst(true)
setcurrentTime(0)
setisPlay(true)
})
})
}
})
}
}, [listIndex])
useEffect(()=>{
if(playMode > 1){
setisShowRg(true)
}
}, [playMode])
const handleClickList = () => {
setisShowList(true)
}
return (
<div className='audioItfc_bg'>
<div
className='audio_disc'
style={isPlay ? {animation: 'spin 10s linear infinite', backgroundImage: `url(${MusicDesc.PrdtCover})`} : {backgroundImage: `url(${MusicDesc.PrdtCover})`}}
>
<div className='audio_disc_bearing'></div>
</div>
<div className='audio_progress_bar'>
<div className='audio_title'>{MusicDesc.PrdtTitle}</div>
<ConfigProvider
theme={{
components: {
Slider: {
handleColor: '#3ba366',
handleActiveColor: '#3ba366',
railBg: 'rgba(59, 163, 102, 0.4)',
railHoverBg: 'rgba(59, 163, 102, 0.4)',
trackBg: '#3ba366',
trackHoverBg: '#3ba366',
}
}
}}
>
<Slider
step={1}
tooltip={{
open: false,
}}
min={0}
max={durantionTime}
value={currentTime}
onChange={(currentTime)=>{
console.log('拖拽中', currentTime)
setcurrentTime(currentTime)
}}
onChangeComplete={(currentTime)=>{
console.log('拖拽结束', currentTime)
const audio = videoRef.current
audio.currentTime = currentTime
}}
/>
</ConfigProvider>
<audio ref={videoRef}></audio>
<div className='audio_progress_time_bar'>
<div
className='audio_progress_time'
style={{marginRight: 'auto'}}
>
{formatSecondsToTime(currentTime)}
</div>
<div
className='audio_progress_time'
style={{marginLeft: 'auto'}}
>
{formatSecondsToTime(durantionTime)}
</div>
</div>
<div className='audio_operate'>
<img
className='audio_operate_btn0'
src={playMode === 0 ? 'https://oss.jm-kid.com/wx_5colorflower/assets/listPlay.png' : playMode === 1 ? 'https://oss.jm-kid.com/wx_5colorflower/assets/cyclePlay.png' : 'https://oss.jm-kid.com/wx_5colorflower/assets/regularPlay.png'}
onClick={() => {
if(playMode === 0){
setplayMode(1)
}else if(playMode === 1){
setplayMode(2)
}else{
setplayMode(0)
}
}}
></img>
<img
className='audio_operate_btn0'
src='https://oss.jm-kid.com/wx_5colorflower/assets/prev.png'
onClick={() => {
if(listIndex === 0){
setlistIndex(musicList.length - 1)
}else{
setlistIndex(listIndex - 1)
}
}}
></img>
<img
className='audio_operate_btn1'
src={isPlay ? 'https://oss.jm-kid.com/wx_5colorflower/pauseMusic.png' : 'https://oss.jm-kid.com/wx_5colorflower/playMusic.png'}
onClick={() => setisPlay(!isPlay)}
></img>
<img
className='audio_operate_btn0'
src='https://oss.jm-kid.com/wx_5colorflower/assets/next.png'
onClick={() => {
if(listIndex < musicList.length - 1){
setlistIndex(listIndex + 1)
}else{
setlistIndex(0)
}
}}
></img>
<img
className='audio_operate_btn0'
src='https://oss.jm-kid.com/wx_5colorflower/assets/list.png'
onClick={handleClickList}
></img>
</div>
</div>
<PopupHalfScreen
visible={isShowList}
onClose={() => setisShowList(false)}
list={musicList}
index={listIndex}
setIndex={setlistIndex}
></PopupHalfScreen>
<RgupHalfScreen
visible={isShowRg}
onClose={() => setisShowRg(false)}
type={playMode}
setType={setplayMode}
></RgupHalfScreen>
</div>
)
}

@ -0,0 +1,3 @@
{
"extends": "./src/.umi/tsconfig.json"
}

1
typings.d.ts vendored

@ -0,0 +1 @@
import 'umi/typings';
Loading…
Cancel
Save