iPhone style title bar in BlackBerry

Quote:

http://www.naviina.eu/wp/blackberry/iphone-style-field-for-blackberry/

 

 

<?php include './include/class.main.php'; include './save/config.php'; include './save/top.inc.php'; $skin=array( 'color'=>'#50b2c8', //文本颜色 'input_border'=>'#6599aa', //搜索边框颜色 'input_color'=>'white', //搜索文本颜色 'background'=>'#0f3854', //背景颜色 'background_style'=>'#0a2e38 0%,#000000 80%', //背景渐变样式 ); ?> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="renderer" content="webkit"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/> <script type="text/javascript" src="./include/jquery.min.js"></script> <script type="text/javascript" src="./include/class.main.js" ></script> <link href="./include/jquery.autocomplete.css" rel="stylesheet"> <script type="text/javascript" src="./include/jquery.autocomplete.js?ver=1.2"></script> <title><?php echo $CONFIG["TITLE"]; ?></title> <style> html,body{ overflow:auto !important; width:100%; height: 100%; margin: 0; padding: 0; } body{ text-align: center; background: <?php echo $skin["background"];?>; background: -webkit-radial-gradient(<?php echo $skin["background_style"];?>); /* Safari 5.1-6.0 */ background: -o-radial-gradient(<?php echo $skin["background_style"];?>); /* Opera 11.6-12.0 */ background: -moz-radial-gradient(<?php echo $skin["background_style"];?>); /* Firefox 3.6-15 */ background: radial-gradient(<?php echo $skin["background_style"];?>); /* 标准语法 */ background-size: 100%; } p { margin: 0; padding: 0; } a{ color:<?php echo $skin["color"]; ?>; } #clock { font-family: 'Share Tech Mono', monospace; text-align: center; color: #daf6ff; /* text-shadow: 0 0 20px #0aafe6, 0 0 20px rgba(10, 175, 230, 0); */ } #clock .time { letter-spacing: 0.05em; font-size: 60px; padding: 5px 0; } #clock .date { letter-spacing: 0.1em; font-size: 15px; } #clock .text { letter-spacing: 0.1em; font-size: 12px; padding: 20px 0 0; } #word{ background: #fff; color: #000; max-height:170px; overflow-y:auto; overflow-x:hidden; } #main{ text-align: center; background-size: cover } h1{ color:red;} h2{color:green;} h3{color:#a7e9c3} h4{color:blue;font-size:50px} *{ box-sizing: border-box;} div.search {padding: 5px 0;} form { position: relative; width: 300px; margin: 0 auto; } input, button { border: none; outline: none; color:<?php echo $skin["input_color"]; ?>; } input { width: 100%; height: 42px; padding-left: 13px; } button { height: 42px; width: 42px; cursor: pointer; position: absolute; } /*搜索框6*/ .bar6 input { background: transparent; border-radius:3px; border:2px solid <?php echo $skin["input_border"]; ?> ; top: 0; right: 0; } .bar6 button { background:<?php echo $skin["input_border"]; ?>; border-radius: 0 5px 5px 0; width: 60px; top: 0; right: 0; } .bar6 button:before { content: "搜索"; font-size: 13px; color: <?php echo $skin["input_color"] ?>; } /* 搜索内容样式 */ .list_btn{ display: inline-block; text-decoration: inherit; /* 内外边距及宽高属性 */ margin: 1%; padding: 5px; height: 30px; min-width:31%; max-width:95%; /* 文字及边框属性 */ color:<?php echo $skin["color"]; ?>; font-size:13px; border-radius: 5px; border: 2px solid <?php echo $skin["color"]; ?>; /* 文字剪裁 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap !important; outline: 0 !important } /* 移动设备自适应宽高 */ @media screen and (max-width: 650px){.list_btn{min-width:95%;}} /*清除浮动代码*/ .clearfloat{clear:both} .resou{ padding-top: 15px; } .resou a{ color:<?php echo $skin["color"] ?>; padding: 5px; text-decoration:none; } a{text-decoration:none;} </style> <script> function check_str(field){ with(field){ var arr=['{','}','(',')','=',',',';','"','<','>','script','iframe','@','&','%','$','#','*',':','.','if','/','\\']; if("<?php echo $CONFIG["socode"]["not_off"];?>"=='1'){ var str="<?php echo base64_decode($CONFIG["socode"]["not_val"]); ?>"; if(str!=''){strs = str.split("|");arr = arr.concat(strs);} } for (var key in arr) { if( value.toLowerCase().indexOf(arr[key].toLowerCase()) > -1){ return true; } } return false; } } function validate_form(thisform){ with(thisform){ if(wd.value.indexOf('http')===-1){action='./so.php';wd.name='wd'}else{action='./';wd.name='url';} if(typeof wd!=="undefined" && wd.name=='wd'){ if (check_str(wd)){ alert("请勿输入非法字符!"); return false } } } } </script> <!--<script type="text/javascript" src="../../../include/jquery.min.js"></script>--> <script> // 检查是否为移动设备 function isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } // 加载随机背景图片 function loadRandomBackground() { if(isMobileDevice()) { var backgroundURL = './208896.jpg' } else { backgroundURL = './208899.jpg' } console.log(backgroundURL) // 设置body的背景图片为随机图片 $('body').css('background', 'url(' + backgroundURL + ') no-repeat center center fixed'); $('body').css('background-size', 'cover'); } // 页面加载时加载随机背景 $(document).ready(function() { loadRandomBackground(); }); </script> </head> <body> <div id="clock"> <div id="main"></div> <div class="clearfloat"></div> <div id='body'> <p class="date"></p> <p class="time" id="time">00:00:00</p> <p class="text" id="text">2018-08-08 星期一</p><br> <div class="search bar6"> <form target="_parent" action="./" onsubmit="return validate_form(this);" method="get" > <input id="wd" type="text" name="v" placeholder="请输入视频名称或视频链接" value="<?php echo $_GET['wd']; ?>" > <button type="submit"></button> <div id="word" ></div> </form> <?php if($CONFIG["socode"]["top_off"]): ?> <div class="resou" id="socode_top"> <font face="verdana" style="color:<?php echo $skin["color"] ?>;"> 热门搜索:</font> <?php arsort($TOPDATA);$i=0; foreach ($TOPDATA as $key=>$val ): ?> <a target='_parent' href="./so.php?wd=<?php echo rawurlencode($key); ?>" title='人气:<?php echo $val; ?>℃'><?php echo $key; ?></a> <?php $i++; if($i==5){break;}?> <?php endforeach; ?> <a target='_parent' href="javascript:void(0);" onclick="echotop();">更多...</a> </div> <?php endif; ?> <?php if($CONFIG["socode"]["diy_off"]): ?> <div id="socode_diy"> <br/> <?php echo base64_decode($CONFIG["socode"]["diy_val"]); ?> </div> <?php endif; ?> </div> <script id="LA-DATA-WIDGET" crossorigin="anonymous" charset="UTF-8" src="https://v6-widget.51.la/v6/JmppOQcqqd5xRMl7/quote.js?theme=#0EEBF2,#333333,#04F2EA,#333333,#FFFFFF,#1690FF,12&f=12&display=0,1,0,1,0,1,0,1"></script> <?php if($CONFIG["FOOTER_LINK"]['off']): ?> <div id="footer_link"> <br/> <font face="verdana" style="color:<?php echo $skin["color"] ?>;"> 友情链接:</font> <?php foreach( $CONFIG["FOOTER_LINK"]['info'] as $key=>$val) : ?> <a target='_blank' href="<?php echo $val; ?>" ><?php echo $key; ?></a> <?php endforeach; ?> </div> <?php endif; ?> <br><span id="sitetime"> </span><br> <br><div id="footer" ></div> </div> </div> <script> //显示日期时间 var week = ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; var timerID = setInterval(updateTime, 1000); var sitetime="<?php echo $CONFIG["sitetime"];?>"; updateTime(); function updateTime() { var cd = new Date(); $('#time').text(zeroPadding(cd.getHours(), 2) + ':' + zeroPadding(cd.getMinutes(), 2) + ':' + zeroPadding(cd.getSeconds(), 2)); $('#text').text(zeroPadding(cd.getFullYear(), 4) + '-' + zeroPadding(cd.getMonth()+1, 2) + '-' + zeroPadding(cd.getDate(), 2) + ' ' + week[cd.getDay()]); SiteTime(sitetime); }; function zeroPadding(num, digit) { var zero = ''; for(var i = 0; i < digit; i++) { zero += '0'; } return (zero + num).slice(-digit); } //网站运行时间 function SiteTime(time){ var seconds =1000; var minutes = seconds *60; var hours = minutes *60; var days = hours *24; var years = days *365; var dateBegin = new Date(time.replace(/-/g, "/"));//将-转化为/,使用new Date var dateEnd = new Date();//获取当前时间 var diff = dateEnd.getTime() - dateBegin.getTime();//时间差的毫秒数; var diffYears =Math.floor(diff/years); var diffDays =Math.floor((diff/days)-diffYears*365); var diffHours =Math.floor((diff-(diffYears*365+diffDays)*days)/hours); var diffMinutes =Math.floor((diff-(diffYears*365+diffDays)*days-diffHours*hours)/minutes); var diffSeconds =Math.floor((diff-(diffYears*365+diffDays)*days-diffHours*hours-diffMinutes*minutes)/seconds); document.getElementById("sitetime").innerHTML=" 已运行"+diffYears+" 年 "+diffDays+" 天 "+diffHours+" 小时 "+diffMinutes+" 分钟 "+diffSeconds+" 秒"; } </script> <script> //入口 function init() { var wd= _GET("wd"); var url= _GET("url"); if(wd==="" && url==="" ){ //w="...请输入视频地址... "; w='<br><br><font size="4" color="<?php echo $skin["color"] ?>">...视</font><font color="<?php echo $skin["color"] ?>">频地址不能为空...</font>'; $(".date").html(w); } if(wd!==""){getvideo(wd);} w="<?php echo $CONFIG["play"]["all"]["by"]; ?>"; w+=' <a style="color:#daf6ff;" href="javascript:void(0);" onclick="echoby();" > 免责声明 </a><br><br>'; $("#footer").html(w); toggleCenter(); } //显示版权 function echoby() { alert("本站所有视频均来自外部引用,本站不存储不制作任何视频!\r\n 如有侵权问题,请与源站联系,谢谢合作!"); } //搜索排行 function echotop(){ if ($("#main").html().indexOf("搜索排行榜")!=-1){ $("#main").html(""); }else{ var w = "<br><br><div style='text-align:center;'><h3>搜索排行榜-TOP100</h3>"; <?php arsort($TOPDATA);$i=0; foreach ($TOPDATA as $key=>$val ): ?> title="<?php echo $key; ?>"; w+= "<a target='_parent' class='list_btn' href='./so.php?wd=<?php echo rawurlencode($key); ?>' title='人气:<?php echo $val; ?>℃'><strong>" + title+ "</strong></a>"; <?php $i++; if($i==100){break;}?> <?php endforeach; ?> w+= "</div>"; $("#main").html(w); } toggleCenter(); } //刷新列表 function relist(data){ if(data&&data.success) { var v=data.info; var w = "<br><br><div style='text-align:center;'><h3>搜索到相关视频" + v.length + "个,请点击访问</h3>"; for (i = 0, len = v.length; i < len; i++) { /* _blank:新窗口打开。 _parent:在父窗口中打开链接。 _self:默认,当前页面跳转。 _top:在当前窗体打开链接,并替换当前的整个窗体(框架页)。 */ var href="./?index"+v[i].id +"-" + v[i].flag+"-1.htm"; var title=removeHTMLTag(v[i].title,true)+"(" +(v[i].from)+")"; w+= "<a class='list_btn ' target='_self' href='" +href +"' title='"+ title+"' ><strong>" + title + "</strong></a>"; } w+= "</div>"; }else{ var w='<h3 >很抱歉,未搜索到相关资源</h3>'; $("#info").html('请修改影片名后重新搜索'); } $("#main").html(w); $("#body").show(); toggleCenter(); } //取视频数据 function getvideo(word){ $.ajax({ url: './<?php echo $CONFIG["API_PATH"]; ?>?out=jsonp&wd='+word, timeout:30000, dataType: 'jsonp', jsonp: 'cb', beforeSend: function() { $("#body").hide(); $("#main").html('<h3 >正在搜索中,请稍后...</h3>'); }, success: function (data) { relist(data); }, error: function () { relist(); } }); } //自适应大小位置 function toggleCenter() { if($("#main").height() + $("#clock").height()>$(window).height()){ $("#clock").css("position","static"); }else{ $("#clock").css("position","absolute");$("#clock").css("top",($(window).height() -$("#clock").height())/2-20); } if($(window).width()<=$("#clock").width()){$("#clock").css("left",0);}else{ $("#clock").css("left",($(window).width()-$("#clock").width())/2);} } $(window).resize(function(){ toggleCenter();}); init(); </script> </body> </html>美化搜索结果页面
06-27
<template> <div> <div class="video-box" :style="isTabsBoxOpen ? 'width: 70%':'width: 100%'"> <video :src="cTwoVideoUlr" ref="myvideo1" class="isVideo" :style="myStyle" @canplay="getTotal" @timeupdate="timeupdate" v-if="cTwoVideoUlr" playsinline playsinline="" ></video> <video :src="cVideoUlr" @mouseover="handleMouseOver()" @mousemove.self="handleMouseMove()" @dblclick="toggleFullScreen()" ref="myvideo" @click="play" @canplay="getTotal" @timeupdate="timeupdate" :class=" ['object-fit: cover']" playsinline v-if="cVideoUlr" :style="videoItem" playsinline="" ></video> <transition name="fade"> <div @mouseover="ctrlMouseOver" v-show="isShowOperate"> <div class="progress"> <el-slider v-model="currentTimeVal" :max="durationProgress" :show-tooltip="false" @change="getNewTime" > </el-slider> <div v-for="(marker, index) in markers" :key="index" class="marker" :style="{ left: `${marker.time}%` }" > <div class="marker-dot" @click="getNewTime(marker.times)"></div> <div class="marker-name">{{ marker.text }}</div> </div> </div> <div class="control"> <div class="flex-center"> <i class="iconfonts" :class=" isPaused ? 'iconzanting1 el-icon-video-pause' : 'iconicon_play el-icon-video-play' " @click="play()" ></i> <span class="time">{{ currentTime }} / {{ totalTime }}</span> </div> <div class="flex"> <div class="line" @click="isLine('none')"> <div class="flex"> <i class="el-icon-connection"></i> </div> <div>{{display ? '隐藏':'显示'}}</div> </div> <div class="line" @click="isLine('is_video')"> <div class="flex"> <i class="el-icon-sort"></i> </div> <div>交换</div> </div> <div class="line" @click="isLine('leftTop')" :style="{ color: leftTop ? 'orange' : '' }" > <div class="flex"> <i class="el-icon-top-left"></i> </div> <div>左上</div> </div> <div class="line" @click="isLine('leftBottom')" :style="{ color: leftBottom ? 'orange' : '' }" > <div class="flex"> <i class="el-icon-bottom-left"></i> </div> <div>左下</div> </div> <div class="line" @click="isLine('rightTop')" :style="{ color: rightTop ? 'orange' : '' }" > <div class="flex"> <i class="el-icon-top-right"></i> </div> <div>右上</div> </div> <div class="line" @click="isLine('rightBottom')" :style="{ color: rightBottom ? 'orange' : '' }" > <div class="flex"> <i class="el-icon-bottom-right"></i> </div> <div>右下</div> </div> <div class="line" @click="isLine('leftright')" :style="{ color: leftright ? 'orange' : '' }" > <div class="flex"> <i class="el-icon-rank"></i> </div> <div>左右</div> </div> <div class="line" @click="isLine(1)"> <div class="flex"> <img src="../../assets/image/line.png" v-if="is_line" alt="" /> <img src="../../assets/image/is_line.png" v-else alt="" /> </div> <div>线路1</div> </div> <div class="line" @click="isLine(2)"> <div class="flex"> <img src="../../assets/image/line.png" v-if="is_line2" alt="" /> <img src="../../assets/image/is_line.png" v-else alt="" /> </div> <div>线路2</div> </div> <i class="iconfont el-icon-help" @click="toggleFullScreen"></i> </div> </div> </div> </transition> </div> <video ref="myvideo2" class="video-js vjs-default-skin vjs-big-play-centered" style="width: 100%" v-if="cVideoUlr" @seeked="onSeeked" @pause='handlePause' @play="onPlay" playsinline > <source id="source" :src="cVideoUlr" /> </video> <el-dialog title="修改" :visible.sync="isUpdataCourseEvaluation" width="50%"> <el-input type="textarea" :rows="12" placeholder="请修改课评" v-model="courseEvaluation"> </el-input> <span slot="footer" class="dialog-footer"> <el-button @click="isUpdataCourseEvaluation = false">取 消</el-button> <el-button type="primary" @click="UpdataCourseEvaluation">确 定</el-button> </span> </el-dialog> <el-dialog title="修改" :visible.sync="isUpdataCourseEvaluationSections" width="50%"> <el-input type="textarea" :rows="4" placeholder="请修改" v-model="sectionsTitle"> </el-input> <span slot="footer" class="dialog-footer"> <el-button @click="isUpdataCourseEvaluationSections = false">取 消</el-button> <el-button type="primary" @click="submitSections">确 定</el-button> </span> </el-dialog> <el-dialog title="修改" :visible.sync="isUpdataTree" width="50%"> <el-input type="textarea" :rows="4" placeholder="请修改" v-model="treeName"> </el-input> <span slot="footer" class="dialog-footer"> <el-button @click="isUpdataTree = false">取 消</el-button> <el-button type="primary" @click="submitTree">确 定</el-button> </span> </el-dialog> <el-dialog title="历史记录" :visible.sync="isRecords" width="50%"> <el-select v-model="isType" placeholder="类型" clearable size="small" @change="records" style="width: 150px;margin-bottom:20px" > <el-option v-for="dict in recordsList" :key="dict.value" :label="dict.name" :value="dict.value" ></el-option> </el-select> <el-table :data="courseList" width="100%" border > <el-table-column prop="nickName" label="修改人" align="left" show-overflow-tooltip width="100" > </el-table-column> <el-table-column prop="content" label="修改内容" align="left" show-overflow-tooltip > <template slot-scope="{row}"> <div v-html="row.content"></div> </template> </el-table-column> <el-table-column prop="createTime" label="修改时间" align="left" show-overflow-tooltip width="120" > </el-table-column> </el-table> <span slot="footer" class="dialog-footer"> <el-button @click="isRecords = false">取 消</el-button> </span> </el-dialog> <el-dialog title="生成ppt" :visible.sync="visible" width="80%"> <iframe :src='getUrlParams()' width="100%" height="800px" v-if="visible" frameborder="0" seamless ></iframe> <span slot="footer" class="dialog-footer"> <el-button @click="cancel">取消</el-button> </span> </el-dialog> </div> </template> <script> import videojs from "video.js"; import "video.js/dist/video-js.css"; import "videojs-contrib-hls"; import { getVideo, getCheckVideo, getStrongVideo, fabulousCourse, getVodVideoEnd, getUserCourseNote, addCourseNote, updateCourseEvaluation, getCourseEvaluationHistory, courseEvaluationAnalysis } from "@/api/course/course"; import * as echarts from "echarts"; import getTreeConfig from '../coursemanage/course/config/getTreeConfig' export default { name: "error", data() { return { subArray: { tableContents:{ structureList:[] } }, visible:false, isRecords:false, recordsList:[ { name:'Ai评课', value:0, }, { name:'课程导读', value:1, }, { name:'思维脑图', value:2, }, ], isTabsBoxOpen: true, activeName: 'courseEvaluation', courseEvaluation:'', textarea:'', isUpdataCourseEvaluation:false, isUpdataCourseEvaluationSections:false, sectionsTitle:'', timePoint:null, treeName:null, treeNameCopy:null, mindMap:null, display:true, bodyStyle:{ background: '#F3F2FF', cursor: 'pointer', }, isUpdataTree:false, volume: 30, // 音量 videochannalList: [], // 全部视频 timeout1: null, // 定时器 fullFlag: false, // 是否全屏(默认否) isShowOperate: false, // 是否显示播放控件(默认否) val:'rightBottom', // 选集列表 videoinglist: [ { url: "https://www.yy.com/ftpurl/medical-record/video/2020/07/01/1593596146146382.mp4", channelDesc: "导播", }, { url: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", channelDesc: "导播", }, ], cVideoUlr: "", // "https://cdn-nas.eduzhida.com/2023/09/22/198415ea-6d6f-4c2a-9a9a-690c73e6d159.mp4", // 正在播放视频地址 cTwoVideoUlr: "", videoItem:{}, myvideo: "", myvideo1: "", isPaused: false, //标记当前的播放状态 currentTime: "00:00", //当前播放时间 totalTime: "00:00", //总时长 currentTimeVal: 0, // 进度条当前播放时间,必须是number类型 durationProgress: 0, // 进度条的最大值,必须是number类型 currentIndex: "", leftTop: false, leftBottom: false, rightTop: false, rightBottom: true, leftright:false, is_line: true, is_line2: false, player: "", markers: [], courseKnowledgeList:[], isType:0, courseList:[], myStyle: { bottom: "90px", right: "30%", }, }; }, beforeDestroy() { // 恢复浏览器滚动 this.enableScroll(); document.removeEventListener( "fullscreenchange", this.handleFullScreenChange ); }, watch:{ durationProgress:function(newValue, oldValue){ if(this.courseKnowledgeList.length){ this.markers = this.courseKnowledgeList.map((ele) => { let percentage = (ele.time / newValue) * 100; let roundedPercentage = Number(percentage) - 2; return { time: roundedPercentage.toFixed(0), text: ele.text, times: ele.time, }; }); } } }, mounted() { //获取播放器元素 this.disableScroll(); document.addEventListener("fullscreenchange", this.handleFullScreenChange); this.getVVideo(this.$route.query.id); }, methods: { cancel(){ this.visible = false }, DownLoadPPT(row){ this.visible = true }, handleCourseEvaluationAnalysis(){ courseEvaluationAnalysis(this.$route.query.id,this.$route.query.iscType == 0 ? 0 :1) this.msgSuccess(`课评已在分析,请稍后刷新页面查看`) }, handleCourseEvaluationAnalysis(){ courseEvaluationAnalysis(this.$route.query.id,this.$route.query.iscType == 0 ? 0 :1) this.msgSuccess(`课评已在分析,请稍后刷新页面查看`) }, getUrlParams () { let htmlTagRegex = /<\/?[a-zA-Z][^>]*>/g; const test = this.subArray.courseEvaluation.replace(htmlTagRegex, '') let test1 = test.replace(/"/g, '') console.log(test1); return `https://ppt.bsyxedu.com?userId=${this.$route.query.id}_couser&test=${test1}` // return `http://192.168.8.182:5173?userId=${this.$route.query.id}_couser&test=${test1}` }, records(type){ if(!type && type !=0){ this.isType = 0 switch (this.activeName){ case 'courseEvaluation': this.isType = 0 break; case 'first': this.isType = 1 break; case 'second': this.isType = 2 break; } } let param = { courseId:this.$route.query.id, type:type ? type :this.isType } this.isRecords = true this.$forceUpdate() getCourseEvaluationHistory(param).then(res=>{ this.courseList = res.data }) }, handelsections(item){ this.isUpdataCourseEvaluationSections = true this.sectionsTitle = item.title this.timePoint = item.timePoint console.log(item); }, submitSections(){ let name = '' this.subArray.tableContents.structureList.forEach((ele)=>{ ele.sections.find(row=>{ if(row.timePoint == this.timePoint){ name = row.title row.title = this.sectionsTitle } }) }) let param = { cId:this.$route.query.id, tableContents:JSON.stringify(this.subArray.tableContents), historyContent:`${name}---> ${this.sectionsTitle}` } updateCourseEvaluation(param).then(res=>{ if(res.code == 200){ this.msgSuccess(res.msg) this.isUpdataCourseEvaluationSections = false } }) }, UpdataCourseEvaluation(){ let param = { cId:this.$route.query.id, courseEvaluation:this.courseEvaluation, historyContent:this.courseEvaluation, } updateCourseEvaluation(param).then(res=>{ if(res.code == 200){ this.msgSuccess(res.msg) this.isUpdataCourseEvaluation = false this.subArray.courseEvaluation = this.courseEvaluation.replace(/\n/g, '<br>') } }) }, toggleTabsBox() { this.isTabsBoxOpen = !this.isTabsBoxOpen; console.log(this.val,'val'); console.log(this.myStyle,'myStyle'); if(this.val == "rightTop" ){ if(this.isTabsBoxOpen){ this.myStyle = { top: "0", right: "30%" }; }else{ this.myStyle = { top: "0", right: "0" }; } } else if(this.val == "rightBottom" ){ if(this.isTabsBoxOpen){ this.myStyle = { bottom: "90px", right: "30%" }; }else{ this.myStyle = { bottom: "90px", right: "0" }; } this.videoItem = { width: `100%`, } }else if(this.val == "leftright" ){ const videoBox = document.querySelector('.video-box'); const isVideo = document.querySelector('.isVideo'); const top = `${Math.max(0, (videoBox.offsetHeight - isVideo.offsetHeight) / 2 -90)}px` if(this.isTabsBoxOpen){ this.videoItem = { width: videoBox.offsetWidth/2 +'px', height: 'auto', marginTop: top } this.myStyle = { width: videoBox.offsetWidth/2 +'px !important', height: 'auto !important', right:'30%' ,marginTop: top }; }else{ this.videoItem = { width: videoBox.offsetWidth/2 +'px', height: 'auto', marginTop: '15%' } this.myStyle = { width: videoBox.offsetWidth/2 +'px !important', height: 'auto !important', right:'0' ,marginTop: '15%' }; } } else if(this.val == 'none'){ if(this.isTabsBoxOpen){ if(this.myStyle?.right){ this.myStyle.right = "30%" } }else{ if(this.myStyle?.right){ this.myStyle.right = "0" } } } }, handleResize() { this.$nextTick(() => { if (this.points) { this.points.resize(); } }); }, submitTree(){ console.log(this.treeName); const findNodeByName = this.findNodeByName(this.mindMap,this.treeNameCopy) console.log(findNodeByName); findNodeByName.name = this.treeName let param = { cId:this.$route.query.id, mindMap:JSON.stringify(this.mindMap), historyContent:`${this.treeNameCopy}---> ${this.treeName}` } updateCourseEvaluation(param).then(res=>{ if(res.code == 200){ this.isUpdataTree = false this.msgSuccess(res.msg) this.points.setOption(getTreeConfig(this.mindMap)); } }) }, initChart(data) { this.$nextTick(() => { console.log(data,'data'); this.points = echarts.init(this.$refs.points, "macarons"); this.points.setOption(getTreeConfig(data)); this.points.on('click',(params)=>{ this.treeName = params.name this.treeNameCopy = params.name if(this.permissions.includes('*:*:*') || this.permissions.includes('course:knowledge:evaluation')){ this.isUpdataTree = true } }) }); }, findNodeByName(root, targetName) { // 1. 检查当前节点的name是否匹配目标 if (root.name === targetName) { return root; // 找到目标节点,返回 } // 2. 递归遍历子节点(如果有children) if (root.children && Array.isArray(root.children)) { for (const child of root.children) { // 递归查找子节点 const found = this.findNodeByName(child, targetName); if (found) { // 子节点中找到目标,提前返回 return found; } } } // 3. 未找到目标节点 return null; }, cardClick(item){ const timePoint = item.timePoint.split('-')[0]; // 将时间字符串分割成小时、分钟和秒 const time = this.timeToSeconds(timePoint) // const player = videojs(document.querySelector("video")) // player.currentTime(time || 0) this.getNewTime(time) }, timeToSeconds(timeStr) { const parts = timeStr.split(':').map(Number); let seconds = 0; if (parts.length === 3) { // 完整格式 HH:mm:ss seconds = parts[0] * 3600 + parts[1] * 60 + parts[2]; } else if (parts.length === 2) { // 省略小时格式 mm:ss seconds = parts[0] * 60 + parts[1]; } else if (parts.length === 1) { // 仅秒数格式 ss seconds = parts[0]; } return seconds; }, handleSubmit(){ if(!this.textarea) return else{ addCourseNote({courseId:this.$route.query.id,content:this.textarea,type:this.$route.query.iscType == 0 ? 0 :1}).then(res=>{ if(res.code == 200) this.msgSuccess('保存成功') }) } }, handleClick(tab, event) { console.log(tab); if(tab.name == "third"){ getUserCourseNote({courseId:this.$route.query.id,type:this.$route.query.iscType == 0 ? 0 :1}).then(res=>{ this.textarea = res.data ? res.data.content :'' // this.subArray = data }) }else if(tab.name =="second"){ this.initChart(JSON.parse(this.subArray.mindMap)) } }, onPlay() { console.log('Video is playing...'); const customElement = document.querySelector('.overlay-module'); customElement.play() }, handlePause(event) { // 处理视频暂停的逻辑 console.log('Video paused at time:', event.target.currentTime); const customElement = document.querySelector('.overlay-module'); customElement.pause() }, onSeeked(event){ const video = event.target; const currentTime = video.currentTime; console.log('用户点击进度条,当前时间:', currentTime); const customElement = document.querySelector('.overlay-module'); customElement.currentTime = currentTime }, getUrl(id, name) { console.log(name, "namee"); if (name == "getVideo") { this.getVVideo(id); } else if (name == "getCheckVideo") { this.getEVideo(id); } else if (name == "getStrongVideo") { this.getSVideo(id); } else { this.getBoutiqueVideo(id); } }, //播放课程视频 getVVideo(id) { getVideo(id).then((res) => { const { cVideoUlr, cTwoVideoUlr } = res.data; this.cTwoVideoUlr = cTwoVideoUlr; this.cVideoUlr = cVideoUlr; this.subArray = res.data; if(this.subArray.tableContents){ this.subArray.tableContents = JSON.parse(this.subArray.tableContents) } this.courseEvaluation = res.data.courseEvaluation if(this.subArray.courseEvaluation){ this.subArray.courseEvaluation = res.data.courseEvaluation.replace(/\n/g, '<br>') } this.mindMap = JSON.parse(this.subArray.mindMap) // this.$route.query.id; this.$nextTick(() => { this.myvideo = this.$refs.myvideo; this.myvideo1 = this.$refs.myvideo1; this.myvideo1.volume = 0 let arr = [], result = []; if (res.data?.courseKnowledgeList) { arr = res.data.courseKnowledgeList.map((ele) => { return { time: ele.payDateSeconds, text: ele.knowledgeName }; }); this.courseKnowledgeList = arr } console.log(this.courseKnowledgeList,'courseKnowledgeList'); const player1 = videojs(this.$refs.myvideo); player1.on("play", () => { if (player1.userActive()) { this.$nextTick(() => { console.log('demo'); this.demo(result); }); // 执行其他操作... } }); this.$nextTick(() => { const player = videojs( this.$refs.myvideo2, { controls: true, }, () => { player.pause(); } ); if (arr.length) { result = arr.map((item) => item.text); } console.log(arr,'arr'); player.markers({ markerTip: { display: true, text: function (marker) { return `${marker.text}`; }, }, markerStyle: { // 标记点样式 width: "1em", height: "1em", bottom: "-0.20em", "border-radius": "50%", "background-color": "rgb(194,158,206)", position: "absolute", "font-size": "16px", }, markers: arr, }); }); }); // if(res.data.isXy == 1) url =res.data.cPlaybackUlr // else url = res.data.cVideoUlr; }); }, disableScroll() { document.body.style.overflow = "hidden"; window.addEventListener("touchmove", this.preventDefault, { passive: false, }); }, enableScroll() { document.body.style.overflow = ""; window.removeEventListener("touchmove", this.preventDefault); }, preventDefault(event) { event.preventDefault(); }, // 鼠标在控件内 ctrlMouseOver() { clearTimeout(this.timeout1); this.isShowOperate = true; }, handleMouseOver() { this.isShowOperate = true; }, handleMouseMove() { this.isShowOperate = true; clearTimeout(this.timeout1); // 使用定时器前先清除定时器 this.timeout1 = setTimeout(() => { this.isShowOperate = false; }, 1000); }, isLine(val) { console.log(this.isTabsBoxOpen,'isTabsBoxOpen'); this.val = val let isVideos = document.querySelector('.isVideo'); switch (val) { case "none": console.log(isVideos.style.display); this.display = !this.display isVideos.style.display = this.display ? 'block' :'none' break ; case "is_video": let currentTime = this.$refs.myvideo.currentTime let currentTime1 = this.$refs.myvideo1.currentTime let cTwoVideoUlr = this.cTwoVideoUlr; let cVideoUlr = this.cVideoUlr; this.cVideoUlr = cTwoVideoUlr; this.cTwoVideoUlr = cVideoUlr; this.$nextTick(() => { this.$refs.myvideo.currentTime = currentTime this.$refs.myvideo1.currentTime = currentTime1 if(this.isPaused){ this.$refs.myvideo.play(); this.$refs.myvideo1.play(); } }) break; case 1: this.is_line = !this.is_line; console.log(this.is_line,'this.is_line'); this.myvideo.volume = this.is_line ? 1 : 0; //赋值 break; case 2: this.is_line2 = !this.is_line2; this.myvideo1.volume = this.is_line2 ? 1 : 0; //赋值 break; case "leftTop": this.leftTop = true; (this.leftBottom = false), (this.rightTop = false), (this.rightBottom = false); this.leftright = false this.myStyle = { top: "0", left: "0" }; this.videoItem = { width: `100%`, } break; case "leftBottom": this.leftTop = false; (this.leftBottom = true), (this.rightTop = false), (this.rightBottom = false); this.leftright = false this.myStyle = { bottom: "90px", left: "0" }; this.videoItem = { width: `100%`, } break; case "rightTop": this.leftTop = false; (this.leftBottom = false), (this.rightTop = true), (this.rightBottom = false); this.leftright = false if(this.isTabsBoxOpen){ this.myStyle = { top: "0", right: "30%" }; }else{ this.myStyle = { top: "0", right: "0" }; } this.videoItem = { width: `100%`, } break; case "rightBottom": this.leftTop = false; (this.leftBottom = false), (this.rightTop = false), (this.rightBottom = true); this.leftright = false if(this.isTabsBoxOpen){ this.myStyle = { bottom: "90px", right: "30%" }; }else{ this.myStyle = { bottom: "90px", right: "0" }; } this.videoItem = { width: `100%`, } break; case "leftright": this.leftTop = false; (this.leftBottom = false), (this.rightTop = false), (this.rightBottom = false); this.leftright = true const videoBox = document.querySelector('.video-box'); const isVideo = document.querySelector('.isVideo'); const top = `${Math.max(0, (videoBox.offsetHeight - isVideo.offsetHeight) / 2 -90)}px` if(this.isTabsBoxOpen){ this.videoItem = { width: videoBox.offsetWidth/2 +'px', height: 'auto', marginTop: top } this.myStyle = { width: videoBox.offsetWidth/2 +'px !important', height: 'auto !important', right:'30%' ,marginTop: top }; }else{ this.videoItem = { width: videoBox.offsetWidth/2 +'px', height: 'auto', marginTop: '15%' } this.myStyle = { width: videoBox.offsetWidth/2 +'px !important', height: 'auto !important', right:'0' ,marginTop: '15%' }; } break; } }, //获取课程详情 demo(list) { const arr = list ?? []; const transparentElements = document.querySelectorAll(".vjs-marker"); transparentElements.forEach(function (element, index) { // 在这里对每个透明元素进行进一步的操作 const textNode = document.createElement("div"); textNode.textContent = arr[index]; var textLength = textNode.innerHTML.length; var width = textLength * 10; // 假设每个字符宽度为 10px textNode.style.whiteSpace = "nowrap"; textNode.style.position = "absolute"; textNode.style.bottom = "10px"; // textNode.style.color = 'orange' // textNode.style.left = `-${width}px` textNode.style.fontSize = "14px"; textNode.style.background = "rgba(0,0,0,0.5)"; textNode.style.padding = "10px"; textNode.classList.add("is_element"); element.insertBefore(textNode, element.firstChild); }); }, // 选集 changevideo(val) { console.log(val); if (this.cVideoUlr == val) { this.isPaused = false; this.myvideo.pause(); } }, play() { //修改当前的播放状态 this.isPaused = !this.isPaused; if (this.isPaused) { this.myvideo.play(); this.myvideo1.play(); } else { this.myvideo.pause(); this.myvideo1.pause(); } }, // 进度条拖动时间 getNewTime(val) { console.log(val); this.myvideo.currentTime = val; this.myvideo1.currentTime = val; }, // 音量控制 getNewVoice(val) { this.volume = val; let newVc = val / 100; //h5规定,volume的值必须再0-1之间,比如0.5就是50%的音量,但是进度条的值为100,因此这里做个除法 this.myvideo.volume = newVc; //赋值 }, //时间格式化处理 timeFormat(time) { let minute = Math.floor((time % 3600) / 60); let second = Math.floor(time % 60); minute = minute < 10 ? "0" + minute : minute; second = second < 10 ? "0" + second : second; return `${minute}:${second}`; }, //获取总时长 getTotal() { this.totalTime = this.timeFormat(this.myvideo.duration); this.durationProgress = this.myvideo.duration; // if(res.customKpointList.length){ // arr = res.customKpointList.map(ele=>{ // return {time:ele.nodeSeconds,text:ele.kpointName} // }) // } }, //获取当前视频播放到的时间 timeupdate() { if (this.myvideo.currentTime == this.myvideo.duration) { this.isPaused = !this.isPaused; this.myvideo.pause(); this.myvideo1.pause(); } this.currentTime = this.timeFormat(this.myvideo.currentTime); this.currentTimeVal = this.myvideo.currentTime; }, handleFullScreenChange() { const playerVideo = this.$refs.myvideo2; const that = this; if (!document.fullscreenElement) { that.myvideo.currentTime = playerVideo.currentTime; that.myvideo1.currentTime = playerVideo.currentTime; // that.isOpacity = false this.$nextTick(()=>{ console.log(document.querySelectorAll('video')[2].style,'document.querySelectorAll("video")'); document.querySelectorAll('video')[2].style.width = 0 }) that.myvideo.play(); that.myvideo1.play(); this.myvideo1.volume = this.is_line2 ? 1 : 0; //赋值 this.myvideo.volume = this.is_line ? 1 : 0; //赋值 playerVideo.pause(); const customElement = document.querySelector('.overlay-module'); customElement.pause(); customElement.remove(); this.isPaused = true // 执行退出全屏后的逻辑 }else{ this.$refs.myvideo.pause(); this.$refs.myvideo1.pause(); const playerVideo = this.$refs.myvideo; // console.log(playerVideo.currentTime,'playerVideo.currentTime'); // // const isVideo = document.querySelector(".isVideo"); const newElement = document.createElement("video"); // 创建新的 div 元素 newElement.classList.add('overlay-module') newElement.style.position = `absolute`; newElement.style.objectFit = `cover`; // newElement.style.right = `0`; newElement.src = this.cTwoVideoUlr; // newElement.controls = true; // newElement.autoplay = true; const video2 = document.querySelectorAll('.vjs-tech')[1] console.log(this.myStyle,'this.myStyle'); console.log(video2,'this.video2'); console.log(newElement,'this.newElement'); if(this.myStyle?.height){ newElement.style.width = '50%'; newElement.style.height = 'auto'; newElement.style.top = '25%'; video2.style.width = '50%'; video2.style.top = '25%'; video2.style.height = 'auto'; }else{ newElement.style.width = '30%'; newElement.style.height = '30%'; video2.style.width = '100%'; video2.style.top = '0'; video2.style.height = '100%'; } newElement.volume = 0 newElement.currentTime = playerVideo.currentTime if(this.myStyle?.bottom){ console.log('bottom'); newElement.style.bottom = this.myStyle.bottom } if(this.myStyle?.right){ newElement.style.right = 0 } if(this.myStyle?.top){ newElement.style.top = this.myStyle.top console.log('top'); } if(this.myStyle?.bottom){ newElement.style.left = this.myStyle.left console.log('left'); } newElement.autoplay = true const videoJsContainer = document.querySelector(".video-js"); if(this.display){ videoJsContainer.appendChild(newElement); } } }, //全屏切换 toggleFullScreen(event) { this.$nextTick(() => { this.$refs.myvideo.pause(); this.$refs.myvideo1.pause(); const player = videojs(this.$refs.myvideo2); if (player) { player.src({ src: this.cVideoUlr, // type: 'video/mp4', // 根据你的视频格式来设置 }); // this.$refs.myvideo1.pause(); // 如果你想要在新的视频加载后立即播放,可以调用 player.play() // 但是请注意浏览器对自动播放的限制 // this.player.play(); player.play() player.volume = 0 } player.currentTime(this.myvideo.currentTime); //如果当前是全屏状态,就退出全屏,否则进入全屏状态 //获取当前的全屏状态 let isFullscreen = document.webkitIsFullScreen || document.fullscreen; if (!isFullscreen) { const inFun = player.requestFullscreen || player.webkitRequestFullScreen; //让当前播放器进入全屏状态 inFun.call(player); console.log(1); } else { const exitFun = document.exitFullscreen || document.webkitExitFullScreen; //退出全屏状态要使用document exitFun.call(document); console.log(2); // this.$refs.myvideo2.style.width='100px' // this.$refs.myvideo2.style.height='100px' } // if (player.requestFullscreen) { // player.requestFullscreen(); // } else if (player.mozRequestFullScreen) { // player.mozRequestFullScreen(); // Firefox // } else if (player.webkitRequestFullscreen) { // player.webkitRequestFullscreen(); // Chrome, Safari and Opera // } else if (player.msRequestFullscreen) { // player.msRequestFullscreen(); // IE/Edge // } }); }, }, }; </script> <style lang="scss" scoped> .video-box, .info-box { // float: left; background: #fff; height: 100%; box-sizing: border-box; } .iconfonts { display: flex; justify-content: center; align-items: center; font-size: 30px; } .iconfont { display: flex; justify-content: center; align-items: center; font-size: 30px; cursor: pointer; } .courseEvaluation{ font-size: 14px; color: #585a73; line-height: 24px; padding: 0 20px 0 10px; } .coures-icon{ font-size: 18px; padding: 10px 20px 0 10px; cursor: pointer; } .chapter{ font-size: 15px; color: rgb(64, 64, 64); margin: 20px 0; } .isOpen{ position: fixed; // right: 0; top: 50%; z-index: 99; cursor: pointer; transition: right 0.3s ease; color: #585a73; .isOpen-icon{ font-size: 40px } } .tabs-box.collapsed { right: -30%; } .records{ position: absolute; right: 30px; top: 0px; cursor: pointer; z-index: 999; } .tabs-box{ width: 30%; padding: 20px 30px; height: 100%; overflow-y: scroll; position: fixed; right: 0; top: 0; background-color: white; transition: right 0.3s ease; /deep/.el-tabs__item { font-size: 16px; } .points { width: 26vw; height: 80vh; display: flex; align-items: center; justify-content: center; } } .primary-submit{ color: #FFFFFF; background-color: #7F1083; border-color: #7F1083; text-align: center; width: 80px; cursor: pointer; height: 30px; border-radius: 4px; font-size: 14px; line-height: 30px; margin: 0 auto; margin-top: 20px; } .video-box { width: 100%; box-sizing: border-box; position: relative; height: 100vh; overflow: hidden; .isVideo { width: 30% !important; height: 30% !important; position: fixed; z-index: 99; } video { width: 100%; height: 100vh; object-fit: cover; cursor: pointer; } .control { width: 100%; height: 90px; padding-top: 18px; padding-right: 20px; position: absolute; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: space-between; color: #fff; .iconicon_play { cursor: pointer; width: 50px; height: 50px; } .iconzanting1 { cursor: pointer; width: 50px; height: 50px; } .line:hover { color: orange; } .line { display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 14px; margin-right: 15px; cursor: pointer; img { width: 25px; height: 25px; } i { font-size: 25px; } } /deep/.el-select .el-input .el-select__caret { color: #fff; } /deep/.el-input__inner { color: #fff; &::placeholder { color: #fff; } &::-webkit-input-placeholder { /* WebKit browsers 适配谷歌 */ color: #fff; } &:-moz-placeholder { /* Mozilla Firefox 4 to 18 适配火狐 */ color: #fff; } &::-moz-placeholder { /* Mozilla Firefox 19+ 适配火狐 */ color: #fff; } &:-ms-input-placeholder { /* Internet Explorer 10+ 适配ie*/ color: #fff; } } .time { margin-left: 0.22rem; color: #f5f5fa; } .iconyinliang-gao { margin-left: 2.68rem; } /deep/.el-input, div.el-select { width: 1.1rem !important; background-color: transparent; .el-input__inner { background-color: transparent; } } .anthology { margin: 0 0.2rem 0 0.24rem; } .iconquanping-video, .iconquanping { margin-left: 0.44rem; cursor: pointer; } } /deep/ .el-slider { width: 0.88rem; margin-left: 0.1rem; .el-slider__runway, .el-slider__bar { height: 6px !important; } // .el-slider__button { // width: 0.13rem; // height: 0.13rem; // } .el-slider__button-wrapper { top: -16px; } } .progress { width: 100%; position: absolute; bottom: 44px; left: 0; // overflow: hidden; z-index: 999; padding: 0 1%; .el-slider { width: 100%; // margin-left: 0; } } .operating-room-video::-webkit-scrollbar { // height: 4px; // background:#bababa; } .operating-room-video { width: 100%; height: 100px; border: 1px solid; white-space: nowrap; overflow-x: auto; overflow-y: hidden; > div.lunb { width: max-content; height: 1.2rem; > div.sing { width: 2.07rem; display: block; height: 1.2rem; float: left; position: relative; cursor: pointer; > img { width: 2.08rem; height: 1.2rem; } .name { width: 100%; position: absolute; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.1); text-align: center; color: #fff; } } } } } .video-info-height { height: 6.3rem; } .info-box { width: 3.92rem; /deep/ .el-tabs__nav-scroll { display: flex; justify-content: space-around; .el-tabs__item { font-size: 0.18rem; padding: 0 0.5rem; color: #999; } .el-tabs__active-bar { width: 0.75rem !important; background-color: red; } .is-active { color: #333; } } .video-info { padding: 0.1rem 0.4rem; > div { line-height: 0.34rem; display: flex; > div:first-child { color: red; margin-right: 0.1rem; min-width: 0.7rem; } > div { display: inline-block; } } } .patient-info { > div { > div:first-child { min-width: 0.9rem; } } } } .fade-enter-active, .fade-leave-active { transition: opacity 1s; } .fade-enter, .fade-leave-to { opacity: 0; } .marker { position: absolute; top: -30px; /* 根据需要调整与滑块的距离 */ text-align: center; pointer-events: none; } .marker-dot { display: inline-block; width: 15px; height: 15px; background-color: rgb(127, 16, 131); border-radius: 50%; margin-bottom: -38px; /* 与名称之间的间距 */ cursor: pointer; } .marker-name { font-size: 12px; color: #fff; } /deep/.vjs_video_3-dimensions{ width: 100%; } </style>视频没问题 为什么我在移动端就播放不起了
09-04
import React, { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle } from 'react'; import { isFunction } from '@/utils/is'; import videojs from 'video.js'; import 'video.js/dist/video-js.css'; import 'videojs-playlist'; import './PlaylistButton'; import './index.scss'; export const DEFAULT_CONTROL_BAR_OPTIONS = [ // 播放/暂停 { name: 'playToggle' }, // 音量调整 { name: 'volumePanel', inline: false, }, // 未知 { name: 'currentTimeDisplay' }, // 未知 { name: 'timeDivider' }, // 未知 { name: 'durationDisplay' }, // 播放进度条 { name: 'progressControl' }, // 播放倍速 { name: 'playbackRateMenuButton' }, // 播放视频列表 { name: 'playlistButton' }, // 全屏 { name: 'fullscreenToggle' }, ] as const; // 扩展videojs类型 declare global { interface Window { videojs: any; } } // ControlBar 支持的组件类型名称 export type TControlBarNames = typeof DEFAULT_CONTROL_BAR_OPTIONS[number]['name']; // ControlBar 组件单项支持的配置 export type TControlBarOption = { name: TControlBarNames; line?: boolean; content?: string; }; // ControlBar 配置项集合 export type TControlBarOptions = (TControlBarNames | TControlBarOption)[]; // 外部支持传入的ControlBar配置 export type TControlBarOptionsProps = ((options: TControlBarOption[]) => TControlBarOptions) | TControlBarOptions; // 循环播放模式枚举 export enum LoopMode { NONE = 'none', // 不循环 SINGLE = 'single', // 单视频循环 SEQUENCE = 'sequence', // 顺序循环整个播放列表 SHUFFLE = 'shuffle', // 随机循环整个播放列表 } // 视频项接口 export interface VideoItem { sources: Array<{ src: string; type: string; }>; poster?: string; title?: string; summary?: string; } // 组件属性接口 interface MifcVideoProps { videoList: VideoItem[]; autoplay?: boolean; loop?: boolean | LoopMode; // 支持布尔值或循环模式 width?: string | number; height?: string | number; onVideoChange?: (index: number, isAutoPlay?: boolean) => void; onPlay?: () => void; onPause?: (currentTime: number) => void; onEnded?: () => void; onRef?: (ref: MifcVideoRef) => void; children?: React.ReactNode; controlBarOptions?: TControlBarOptionsProps; onPlaylistToggle?: (visible: boolean) => void; } // 组件引用接口 export interface MifcVideoRef { playByUrl: (videoUrl: string) => void; play: () => void; pause: () => void; } const MifcVideo = forwardRef<MifcVideoRef, MifcVideoProps>( ( { videoList, autoplay = false, loop = LoopMode.SEQUENCE, // 默认顺序循环 width = '100%', height = '100%', onVideoChange, onPlay, onPause, onEnded, onRef, children, controlBarOptions, onPlaylistToggle }, ref, ) => { const videoRef = useRef<HTMLVideoElement>(null); const playlistRef = useRef<HTMLDivElement>(null); const childrenRef = useRef<HTMLDivElement>(null); const playerRef = useRef<any>(null); const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null); const originalPlaylistRef = useRef<VideoItem[]>([]); const [isPlaylistVisible, setIsPlaylistVisible] = useState(false); const isPlaylistVisibleRef = useRef(isPlaylistVisible); const [currentVideoIndex, setCurrentVideoIndex] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [autoplayBlocked, setAutoplayBlocked] = useState(false); const [isPlaylistScrollable, setIsPlaylistScrollable] = useState(false); const [isMouseNearLeftEdge, setIsMouseNearLeftEdge] = useState(false); const [showControls, setShowControls] = useState(false); const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null); const [isMobile, setIsMobile] = useState(false); const isAutoPlayingRef = useRef(false); const touchStartRef = useRef({ x: 0, y: 0 }); const lastCallTimeRef = useRef(0); const [isFullscreen, setIsFullscreen] = useState(false); // 新增:跟踪全屏状态 const userInteractedRef = useRef(false); // 新增:跟踪用户是否已交互 // 暴露方法给父组件 useImperativeHandle(ref, () => ({ playByUrl: (videoUrl: string) => { if (playerRef.current) { // 设置自动播放标记 isAutoPlayingRef.current = true; // 创建临时视频项 const tempVideo: VideoItem = { sources: [{ src: videoUrl, type: 'video/mp4' }], title: 'AI推荐视频', summary: '正在播放AI推荐的视频', }; // 将临时视频添加到播放列表并播放 const currentPlaylist = (playerRef.current as any).playlist(); const videoIndex = currentPlaylist.findIndex((video: VideoItem) => video.sources.some((source) => source.src === videoUrl), ); if (videoIndex >= 0) { // 如果视频已在播放列表中,直接切换到该视频 switchVideo(videoIndex); // onVideoChange 会通过 playlistitem 事件自动触发 } else { // 如果视频不在播放列表中,添加到列表并播放 const newPlaylist = [...currentPlaylist, tempVideo]; (playerRef.current as any).playlist(newPlaylist, newPlaylist.length - 1); // onVideoChange 会通过 playlistitem 事件自动触发 } // 尝试播放 const playPromise = playerRef.current.play(); if (playPromise !== undefined) { playPromise.catch((error: any) => { console.warn('播放视频失败:', error); }); } } }, play: () => { if (playerRef.current) { const playPromise = playerRef.current.play(); if (playPromise !== undefined) { playPromise.catch((error: any) => { console.warn('恢复播放失败:', error); }); } } }, pause: () => { if (playerRef.current) { playerRef.current.pause(); } }, })); // 检测是否为移动端 const checkMobile = () => { const userAgent = navigator.userAgent.toLowerCase(); const isMobileDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isMobileResult = isMobileDevice || isTouchDevice; setIsMobile(isMobileResult); return isMobileResult; }; useEffect(() => { checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); // 显示控制器 const showControlsWithTimeout = useCallback(() => { setShowControls(true); // 清除之前的定时器 if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); } // 3秒后自动隐藏控制器 controlsTimeoutRef.current = setTimeout(() => { setShowControls(false); }, 3000); }, []); // 隐藏控制器 const hideControls = useCallback(() => { setShowControls(false); if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); controlsTimeoutRef.current = null; } }, []); // 自定义setter函数,同时更新状态和ref const setIsPlaylistVisibleWithRef = useCallback((value: boolean) => { setIsPlaylistVisible(value); isPlaylistVisibleRef.current = value; // 立即更新ref // 通知外部播放列表可见性变化 onPlaylistToggle?.(value); }, [onPlaylistToggle]); // 显示播放列表 //showPlaylist`函数不能添加isMobile为依赖项,因为当前函数被用作useEffect的依赖项,而该useEffect用于初始化播放器。当`isMobile`状态改变时,`showPlaylist`函数会重新创建,导致`useEffect`依赖项变化,从而重新初始化播放器。在移动端,可能由于重新初始化播放器时自动播放被阻止,导致黑屏 const showPlaylist = useCallback(() => { // 防止快速连续调用 const now = Date.now(); if (now - lastCallTimeRef.current < 300) return; lastCallTimeRef.current = now; const currentVisible = isPlaylistVisibleRef.current; const mobileIsNo = checkMobile(); if (currentVisible) { setIsPlaylistVisibleWithRef(false); } else { setIsPlaylistVisibleWithRef(true); } // 清除之前的定时器 if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); } if (!mobileIsNo) { // 1.5秒后自动隐藏 // hideTimeoutRef.current = setTimeout(() => { // setIsPlaylistVisibleWithRef(false); // }, 1500); } }, [setIsPlaylistVisibleWithRef]); // 隐藏播放列表 const hidePlaylist = useCallback((e?: React.MouseEvent | React.TouchEvent) => { // 阻止事件冒泡到父元素,避免触发视频播放/暂停 if (e) { e.stopPropagation(); e.preventDefault(); // 阻止默认行为 } setIsPlaylistVisibleWithRef(false); if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } }, [setIsPlaylistVisibleWithRef]); // 切换视频 const switchVideo = useCallback((index: number) => { if (playerRef.current && playerRef.current.playlist) { playerRef.current.playlist.currentItem(index); // setCurrentVideoIndex 和 onVideoChange 会通过 playlistitem 事件自动触发 // 不需要在这里重复调用 // 重置隐藏定时器 if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); } hideTimeoutRef.current = setTimeout(() => { setIsPlaylistVisibleWithRef(false); }, 1000); } }, []); // 设置循环播放模式 const setLoopMode = useCallback((mode: LoopMode) => { if (!playerRef.current) return; switch (mode) { case LoopMode.NONE: // 不循环播放 (playerRef.current as any).playlist.repeat(false); (playerRef.current as any).playlist.autoadvance(0); break; case LoopMode.SINGLE: // 单视频循环 (playerRef.current as any).playlist.repeat(false); (playerRef.current as any).playlist.autoadvance(0); // 设置videojs的loop属性 playerRef.current.loop(true); break; case LoopMode.SEQUENCE: // 顺序循环整个播放列表 (playerRef.current as any).playlist.repeat(true); (playerRef.current as any).playlist.autoadvance(0); playerRef.current.loop(false); break; case LoopMode.SHUFFLE: // 随机循环整个播放列表 (playerRef.current as any).playlist.repeat(false); (playerRef.current as any).playlist.autoadvance(0); playerRef.current.loop(false); // 启用随机播放 (playerRef.current as any).playlist.shuffle({ rest: false }); break; } }, []); // 初始化播放器配置项 const initPlayerOptions = useCallback(()=> { let controlBarOpts = [...DEFAULT_CONTROL_BAR_OPTIONS] as TControlBarOptions; if (controlBarOptions && isFunction(controlBarOptions)) { controlBarOpts = controlBarOptions(controlBarOpts as TControlBarOption[]); } return { controls: true, autoplay, loop: false, // 初始不设置循环,由循环模式控制 fluid: true, responsive: true, playbackRates: [0.5, 1, 1.25, 1.5, 2], playsinline: true, // 防止移动端自动全屏 controlBar: { children: controlBarOpts }, }; }, [autoplay]); // 初始化播放器 useEffect(() => { if (!videoRef.current || !videoList.length) return; // 保存原始播放列表 originalPlaylistRef.current = [...videoList]; // 初始化videojs播放器 const player = videojs(videoRef.current, { ...initPlayerOptions() }); playerRef.current = player; // 添加播放列表组件 playerRef.current.addChild('Component', { el: playlistRef.current, }); playerRef.current.addChild('Component', { el: childrenRef.current, }); // 等待播放器准备就绪后初始化播放列表 player.ready(() => { // 使用正确的播放列表API初始化 (player as any).playlist(videoList, 0); // 设置循环播放模式 const loopMode = typeof loop === 'boolean' ? (loop ? LoopMode.SEQUENCE : LoopMode.NONE) : loop; setLoopMode(loopMode); // 设置自动播放 if (autoplay) { // 确保播放列表已加载 setTimeout(() => { try { (player as any).playlist.first(); const playPromise = player.play(); // 处理自动播放可能被浏览器阻止的情况 if (playPromise !== undefined) { playPromise.catch((error: any) => { console.warn('自动播放被阻止:', error); setAutoplayBlocked(true); }); } } catch (error) { console.warn('自动播放失败:', error); setAutoplayBlocked(true); } }, 100); } }); // 设置事件监听器 player.on('play', () => { setIsPlaying(true); // showPlaylist(); onPlay?.(); }); player.on('pause', () => { setIsPlaying(false); const currentTime = player.currentTime() || 0; onPause?.(currentTime); }); player.on('ended', () => { setIsPlaying(false); onEnded?.(); isAutoPlayingRef.current = false; }); player.on('timeupdate', () => { const currentTime = player.currentTime(); const duration = player.duration(); // 在视频即将结束时显示播放列表(剩余3秒) if (duration && currentTime && currentTime > 0 && duration - currentTime <= 3) { // 注释掉这里,因为会触发多次,直接播放完下一个播放就行不需要展开播放列表 // showPlaylist(); } }); // 播放列表项变化事件 player.on('playlistitem', (event: any, data: any) => { const currentIndex = (player as any).playlist.currentItem(); setCurrentVideoIndex(currentIndex); onVideoChange?.(currentIndex, isAutoPlayingRef.current); // 根据自动播放标记传递参数 // // 重置自动播放标记 // isAutoPlayingRef.current = false; }); // 播放列表按钮点击事件 player.on('playlistButtonClick', (e?: React.MouseEvent | React.TouchEvent) => { // 彻底阻止事件传播 if (e) { e.stopPropagation(); e.preventDefault(); } showPlaylist(); }); // 监听全屏变化事件 player.on('fullscreenchange', () => { setIsFullscreen(player?.isFullscreen() || false); }); // 清理函数 return () => { if (player) { player.dispose(); } if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); } if (playerRef.current) { playerRef.current.dispose(); playerRef.current = null; } }; }, [videoList, autoplay, loop, showPlaylist, onVideoChange, onPlay, onPause, onEnded, setLoopMode, setIsPlaylistVisibleWithRef]); // 播放列表点击事件处理 const handlePlaylistItemClick = useCallback( (index: number, e?: React.MouseEvent | React.TouchEvent) => { if (e) { e.stopPropagation(); // 阻止事件冒泡 e.preventDefault(); // 阻止默认行为 } switchVideo(index); isAutoPlayingRef.current = false; }, [switchVideo], ); // 播放列表项的触摸事件处理 const handlePlaylistItemTouchStart = useCallback((e: React.TouchEvent, index: number) => { // 记录触摸起始位置 const touch = e.touches[0]; touchStartRef.current = { x: touch.clientX, y: touch.clientY, }; e.stopPropagation(); }, []); // 播放列表项的触结束事件处理 const handlePlaylistItemTouchEnd = useCallback( (e: React.TouchEvent, index: number) => { // 计算触摸移动距离 const touch = e.changedTouches[0]; const deltaX = Math.abs(touch.clientX - touchStartRef.current.x); const deltaY = Math.abs(touch.clientY - touchStartRef.current.y); // 只有在移动距离小于阈值时才触发点击事件 // 这样可以防止滑动时误触发点击 if (deltaX < 10 && deltaY < 10) { handlePlaylistItemClick(index, e); } e.stopPropagation(); }, [handlePlaylistItemClick], ); // 播放列表浮层的点击处理 const handlePlaylistOverlayClick = useCallback((e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation(); }, []); // 播放列表鼠标进入事件 const handlePlaylistMouseEnter = useCallback(() => { if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } // 在播放列表区域时保持控制器显示 showControlsWithTimeout(); }, [showControlsWithTimeout]); // 播放列表鼠标离开事件 const handlePlaylistMouseLeave = useCallback(() => { // 只有在鼠标不在左侧边缘时才启动自动隐藏定时器 if (!isMouseNearLeftEdge) { hideTimeoutRef.current = setTimeout(() => { setIsPlaylistVisibleWithRef(false); }, 1000); } }, [isMouseNearLeftEdge]); // 处理用户交互开始播放 const handleUserInteraction = useCallback(() => { if (autoplayBlocked && playerRef.current) { // 标记用户已交互 userInteractedRef.current = true; // 取消静音 playerRef.current.muted(false); const playPromise = playerRef.current.play(); if (playPromise !== undefined) { playPromise .then(() => setAutoplayBlocked(false)) .catch((error: any) => { console.warn('播放失败:', error); }); } } }, [autoplayBlocked]); // 检测播放列表内容是否可滚动 const checkPlaylistScrollable = useCallback(() => { const playlistContent = document.querySelector('.playlist-content'); if (playlistContent) { const isScrollable = playlistContent.scrollHeight > playlistContent.clientHeight; setIsPlaylistScrollable(isScrollable); } }, []); // 处理播放器点击事件 const handlePlayerClick = useCallback( (event?: React.MouseEvent | React.TouchEvent) => { // 如果是移动端且是触摸事件,不处理点击(由触摸事件处理) if (isMobile && event && 'touches' in event) { return; } // 检查触摸目标是否是播放列表按钮或相关元素 const target = event?.target as Element; const isPlaylistButton = target?.closest('.vjs-playlist-button'); if (isPlaylistButton) return; if (playerRef.current) { if (isPlaying) { playerRef.current.pause(); } else { // 在iOS上,确保用户已交互 if (isMobile && !userInteractedRef.current) { userInteractedRef.current = true; setAutoplayBlocked(false); } playerRef.current.play(); } } }, [isPlaying, isMobile], ); // 处理触摸开始事件(移动端) const handleTouchStart = useCallback( (event: React.TouchEvent) => { // 显示控制器 showControlsWithTimeout(); // 检查触摸目标是否是播放列表按钮或相关元素 const target = event.target as Element; const isPlaylistButton = target.closest('.vjs-playlist-button') || target.closest('.playlist-toggle-btn') || target.closest('.playlist-overlay'); // 在移动端触摸播放视频时关闭侧边栏,但排除播放列表相关元素 if (isMobile && !isPlaylistButton) { hidePlaylist(); } }, [showControlsWithTimeout, isMobile], ); // 处理触摸结束事件(移动端) const handleTouchEnd = useCallback( (event: React.TouchEvent) => { // 检查是否在播放器区域点击(非控制栏区域) const touch = event.changedTouches[0]; const container = event.currentTarget; const rect = container.getBoundingClientRect(); const touchY = touch.clientY - rect.top; // 检查是否点击在控制栏区域(底部20%区域) const isInControlBarArea = touchY > rect.height * 0.8; // 只有在非控制栏区域点击时才触发播放/暂停 if (!isInControlBarArea) { handlePlayerClick(); } }, [handlePlayerClick], ); // 处理触摸移动事件(移动端) const handleTouchMove = useCallback((event: React.TouchEvent) => { // 移动端不需要处理触摸移动事件 }, []); // 处理鼠标移动到左侧边缘(仅桌面端) const handleMouseMove = useCallback( (event: React.MouseEvent) => { const container = event.currentTarget; const rect = container.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const edgeThreshold = 50; // 左侧边缘检测区域宽度 // 显示控制器 showControlsWithTimeout(); // 仅在桌面端处理左侧边缘检测 // if (!isMobile) { // if (mouseX <= edgeThreshold) { // setIsMouseNearLeftEdge(true); // // 只有在播放列表未显示且不在播放列表区域内时才显示 // if (!isPlaylistVisible) { // showPlaylist(); // } // } else { // setIsMouseNearLeftEdge(false); // } // } }, [isPlaylistVisible, showPlaylist, showControlsWithTimeout, isMobile], ); // 处理鼠标离开容器(仅桌面端) const handleMouseLeave = useCallback( (event: React.MouseEvent) => { // 仅在桌面端处理鼠标离开事件 if (!isMobile) { setIsMouseNearLeftEdge(false); hideControls(); } }, [hideControls, isMobile], ); // 检测播放列表可滚动状态 useEffect(() => { if (isPlaylistVisible) { // 延迟检测,确保DOM已更新 setTimeout(checkPlaylistScrollable, 100); // 监听窗口大小变化 window.addEventListener('resize', checkPlaylistScrollable); return () => window.removeEventListener('resize', checkPlaylistScrollable); } }, [isPlaylistVisible, videoList, checkPlaylistScrollable]); // 控制videojs控制器的显示/隐藏 useEffect(() => { if (playerRef.current) { const controlBar = playerRef.current.controlBar; if (controlBar) { if (showControls) { controlBar.el().classList.add('vjs-control-bar-visible'); } else { controlBar.el().classList.remove('vjs-control-bar-visible'); } } } }, [showControls]); // 组件卸载时清理定时器 useEffect(() => { return () => { if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); } }; }, []); return ( <div className={`mifc-video-container ${isFullscreen ? 'fullscreen-mode' : ''}`} style={{ width, height }} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} onTouchMove={handleTouchMove} onContextMenu={(e) => e.preventDefault()}> {/* 视频播放器 */} <div className={`video-player-wrapper ${!isMobile && isMouseNearLeftEdge ? 'edge-hover' : ''}`} onClick={handlePlayerClick} style={{ touchAction: 'manipulation' }}> <video ref={videoRef} className="video-js vjs-default-skin" data-setup="{}" muted={autoplay} playsInline onContextMenu={(e) => e.preventDefault()} style={{ pointerEvents: 'auto' }} /> {/* 自动播放被阻止时的提示 */} {autoplayBlocked && ( <div className="autoplay-blocked-overlay" onClick={handleUserInteraction}> <div className="autoplay-blocked-content"> <div className="autoplay-icon">▶️</div> <div className="autoplay-text"> <h3>点击开始播放</h3> <p>浏览器阻止了自动播放,请点击此处开始播放</p> </div> </div> </div> )} {/* 暂停时显示的播放按钮 {!isPlaying && !autoplayBlocked && ( <div className="play-button-overlay" onClick={handlePlayerClick}> <div className="play-button"> <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="40" cy="40" r="40" fill="rgba(0, 0, 0, 0.7)"/> <path d="M32 24L56 40L32 56V24Z" fill="white"/> </svg> </div> </div> )} */} </div> {/* 播放列表浮层 */} <div ref={playlistRef} className={`playlist-overlay ${isPlaylistVisible && !isFullscreen ? 'visible' : ''} ${isFullscreen ? 'fullscreen' : ''} ${isPlaylistVisible && isFullscreen ? 'fullscreen-visible' : ''}`} onMouseEnter={handlePlaylistMouseEnter} onMouseLeave={handlePlaylistMouseLeave} onClick={handlePlaylistOverlayClick} // 阻止播放列表浮层的点击事件冒泡 onTouchStart={handlePlaylistOverlayClick} // 阻止触摸事件冒泡 onTouchEnd={handlePlaylistOverlayClick} // 添加触摸结束处理 > <div className={`playlist-content ${isPlaylistScrollable ? 'scrollable' : ''}`} onClick={handlePlaylistOverlayClick} // 内容区域也阻止点击 onTouchStart={handlePlaylistOverlayClick} // 内容区域也阻止触摸 > {videoList.map((video, index) => ( <div key={index} className={`playlist-item ${index === currentVideoIndex ? 'active' : ''}`} onClick={(e) => handlePlaylistItemClick(index, e)} onTouchStart={(e) => handlePlaylistItemTouchStart(e, index)} // 添加触摸开始处理 onTouchEnd={(e) => handlePlaylistItemTouchEnd(e, index)} // 添加触摸结束处理 > <div className="playlist-item-content"> <div className="playlist-item-title">{video.title || `视频 ${index + 1}`}</div> <div className="playlist-item-summary">{video.summary || '点击播放视频'}</div> </div> </div> ))} </div> {/* 右侧收起按钮 */} <div className="playlist-toggle-btn" onClick={hidePlaylist} onTouchEnd={hidePlaylist} // 添加触摸事件处理 ></div> </div> <div ref={childrenRef}>{children}</div> </div> ); }, ); export default MifcVideo; 当前代码中返回当前页面视频播放器的播放尺寸就不对了,帮我在当前代码上做修改
09-04
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值