play.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <template>
  2. <view class="video-page-wrap flex-column">
  3. <view class="player flex-column">
  4. <view class="back" @click="goBack">
  5. <u-icon name="arrow-left" color="#fff" size="20"></u-icon>
  6. </view>
  7. <!-- <xt-video
  8. v-if="form.playVideoUrl"
  9. ref="xtVideoRef"
  10. video-id="video"
  11. :src="form.playVideoUrl"
  12. :poster="form.poster"
  13. :episodes="episodes"
  14. :ratios="ratios"
  15. @Ratio="handleChangeRatio"
  16. @Epi="handleChangeEpi"
  17. @timeupdate="timeupdate"
  18. :initialTime="form.second"
  19. showSpeed
  20. autoplay
  21. ></xt-video> -->
  22. <sunny-video
  23. style="width: 100%; height: 100%"
  24. ref="sunnyVideo"
  25. :src="form.playVideoUrl"
  26. :poster="form.poster"
  27. @timeupdate="timeupdate"
  28. :trialTime="0"
  29. tipText="试看结束,若想观看全剧请移步首页购买VIP服务"
  30. btnText="充值VIP"
  31. :seekTime="form.second"
  32. showMuteBtn
  33. ></sunny-video>
  34. </view>
  35. <view class="main-content">
  36. <view class="flex justify-between">
  37. <view class="title">{{ form.title }}</view>
  38. <view
  39. class="text-but flex-left justify-center"
  40. @click="isOpen = !isOpen"
  41. >
  42. <text>简介</text>
  43. <u-icon
  44. :name="isOpen ? 'arrow-down' : 'arrow-right'"
  45. color="#696969"
  46. size="10"
  47. bold
  48. ></u-icon>
  49. </view>
  50. </view>
  51. <view class="describe" v-show="isOpen">
  52. {{ form.content }}
  53. </view>
  54. <view class="handle-box flex-left">
  55. <view
  56. @click="goProjection"
  57. class="handle-row flex-left flex-column"
  58. >
  59. <img src="@/static/img/icon-projection.svg" />
  60. <view class="handle-text">投屏</view>
  61. </view>
  62. <view
  63. @click="setCollect"
  64. class="handle-row flex-left flex-column"
  65. >
  66. <img
  67. src="@/static/img/icon-collect-active.svg"
  68. v-if="collectState"
  69. />
  70. <img src="@/static/img/icon-collect.svg" v-else />
  71. <view class="handle-text">{{
  72. collectState ? "取消收藏" : "收藏"
  73. }}</view>
  74. </view>
  75. </view>
  76. <view class="flex justify-between" style="margin-top: 60rpx">
  77. <view class="title">选集</view>
  78. <view
  79. class="flex-left justify-center"
  80. @click="showAllEpisode = true"
  81. >
  82. <text>{{ form.plan }}</text>
  83. <u-icon
  84. name="arrow-right"
  85. color="#696969"
  86. size="10"
  87. bold
  88. ></u-icon>
  89. </view>
  90. </view>
  91. <view class="flex" style="overflow-y: hidden; margin-top: 40rpx">
  92. <view
  93. v-for="(item, index) in videoCollectionList"
  94. @click="setEpisode(item, index)"
  95. class="flex-left justify-center gather_but"
  96. ><text
  97. :style="{
  98. color: form.page == index ? '#1DBC46' : '#000',
  99. }"
  100. style="font-size: 26rpx; text-align: center"
  101. >{{ item.name }}</text
  102. >
  103. </view>
  104. </view>
  105. <view style="margin-top: 60rpx">
  106. <view class="title">猜你喜欢</view>
  107. <view class="flex-row flex-wrap" style="margin-top: 20rpx">
  108. <view
  109. class="videoCardMain flex-column"
  110. @click="goPlayVideo(item)"
  111. v-for="(item, index) in recommendVideoList"
  112. >
  113. <view class="videoCardImgBox">
  114. <image
  115. class="videoCardImg"
  116. :src="item.img"
  117. mode="aspectFill"
  118. ></image>
  119. <text class="video-state">{{ item.state }}</text>
  120. </view>
  121. <view class="videoCardName line_1">
  122. {{ item.title }}
  123. </view>
  124. </view>
  125. </view>
  126. </view>
  127. </view>
  128. <u-action-sheet
  129. title="选集"
  130. @close="showAllEpisode = false"
  131. :show="showAllEpisode"
  132. safeAreaInsetBottom
  133. >
  134. <view
  135. style="height: 800rpx; overflow-x: hidden; padding: 16rpx 20rpx"
  136. >
  137. <view class="flex-row flex-wrap">
  138. <view
  139. v-for="(item, index) in videoCollectionList"
  140. @click="setEpisode(item, index)"
  141. class="flex-left justify-center gather_but"
  142. >
  143. <text
  144. :style="{
  145. color: form.page == index ? '#1DBC46' : '#000',
  146. }"
  147. style="font-size: 26rpx; text-align: center"
  148. >{{ item.name }}</text
  149. >
  150. </view>
  151. </view>
  152. </view>
  153. </u-action-sheet>
  154. <u-popup :show="tpshow" :round="10" closeable @close="tpshow = false">
  155. <view class="tbbox">
  156. <view class="tptitle">
  157. <text class="tptitletext">投屏</text>
  158. </view>
  159. <view
  160. class="tbitem"
  161. v-for="dev in tpdevList"
  162. :key="dev.id"
  163. @click="tpStart(dev.id)"
  164. >
  165. <u-icon
  166. name="wifi"
  167. size="30"
  168. style="margin-right: 15rpx"
  169. ></u-icon>
  170. <text class="tbitemtext">{{ dev.name }}</text>
  171. </view>
  172. <u-empty
  173. v-if="tpdevList.length == 0"
  174. mode="wifi"
  175. icon="http://cdn.uviewui.com/uview/empty/wifi.png"
  176. text="无媒体"
  177. />
  178. <u-button
  179. class="tbbtn"
  180. text="点我刷新列表"
  181. @click="tpSearch()"
  182. shape="circle"
  183. color="linear-gradient(to right, #ff9800, #ff2a14)"
  184. ></u-button>
  185. </view>
  186. </u-popup>
  187. <u-overlay :show="showPoster">
  188. <div class="poster">
  189. <view class="icon-close" @click="showPoster = false">
  190. <u-icon name="close" color="#fff" size="28"></u-icon>
  191. </view>
  192. <img src="@/static/img/poster.png" />
  193. </div>
  194. </u-overlay>
  195. </view>
  196. </template>
  197. <script>
  198. // const dlna = uni.requireNativePlugin('JX-Dlna');
  199. import $req from "@/service/serviceConfig";
  200. import { listPackage } from "@/utils/apiMethods.js";
  201. export default {
  202. data() {
  203. return {
  204. form: {
  205. url: "",
  206. page: 0, //当前集
  207. second: 0, //当前播放秒
  208. title: "",
  209. content: "",
  210. plan: "",
  211. playVideoUrl: "",
  212. },
  213. isOpen: false, //是否打开简介
  214. recommendVideoList: [], //推荐的视频列表
  215. videoCollectionList: [], //视频合集列表
  216. showAllEpisode: false,
  217. collectState: false, //是否收藏
  218. currentTime: 0, //当前播放时间
  219. tpshow: false,
  220. tptitle: "",
  221. tpdevList: [],
  222. ratios: [
  223. // {
  224. // id: "480p",
  225. // url: "xxx.mp4",
  226. // name: "标清",
  227. // },
  228. // {
  229. // id: "720p",
  230. // url: "xxx1.mp4",
  231. // name: "高清",
  232. // },
  233. // {
  234. // id: "1080p",
  235. // url: "xxx2.mp4",
  236. // name: "超清",
  237. // },
  238. ],
  239. episodes: [
  240. // {
  241. // id: "1",
  242. // url: "xxx.mp4",
  243. // name: "1",
  244. // isVip: false,
  245. // },
  246. // {
  247. // id: "2",
  248. // url: "xxx.mp4",
  249. // name: "2",
  250. // isVip: false,
  251. // },
  252. // {
  253. // id: "3",
  254. // url: "xxx.mp4",
  255. // name: "3",
  256. // isVip: true,
  257. // },
  258. ],
  259. showPoster: false,
  260. };
  261. },
  262. async onLoad(option) {
  263. let vm = this;
  264. vm.form.url = option.url;
  265. uni.showLoading({
  266. title: "加载中",
  267. });
  268. await vm.getVideoBasicInfo();
  269. await vm.getVideoDetail();
  270. vm.getform(vm.videoCollectionList[vm.form.page].url);
  271. uni.hideLoading();
  272. },
  273. // 监听页面隐藏事件
  274. onHide() {
  275. let vm = this;
  276. vm.recordVideos();
  277. },
  278. methods: {
  279. // 自定义切换分辨率方法
  280. handleChangeRatio(item) {
  281. this.$refs.xtVideoRef.xtVideoReload({
  282. url: item.url,
  283. });
  284. },
  285. // 自定义切换集数方法
  286. handleChangeEpi(item) {
  287. this.$refs.xtVideoRef.xtVideoReload({
  288. url: item.url,
  289. startTime: 0,
  290. });
  291. },
  292. goProjection() {
  293. // uni.showToast({
  294. // icon: "none",
  295. // title: "请使用百度浏览器APP播放,然后点击视频右上角投屏按钮",
  296. // duration: 2000,
  297. // });
  298. this.showPoster = true;
  299. },
  300. tpSearch() {
  301. this.tpshow = true;
  302. this.tptitle = "";
  303. this.tpdevList = [];
  304. dlna.search((result) => {
  305. //alert(result.type)
  306. this.tptitle +=
  307. "通知类型:" +
  308. result.type +
  309. " 设备名:" +
  310. result.name +
  311. " 设备Id:" +
  312. result.id +
  313. "<br />";
  314. if (result.type === "add") {
  315. this.tpdevList.push({
  316. id: result.id,
  317. name: result.name,
  318. });
  319. } else {
  320. this.tpdevList = this.tpdevList.filter(
  321. (x) => x.id != result.id
  322. );
  323. }
  324. });
  325. },
  326. tpStart(id) {
  327. this.tpshow = false;
  328. dlna.play(
  329. {
  330. id: id,
  331. url: this.vUrl,
  332. title: this.detail.vod_name,
  333. },
  334. (result) => {
  335. this.tptitle = result.msg;
  336. }
  337. );
  338. },
  339. // 屏幕共享触发的方法
  340. startScreenSharing() {
  341. // 检查浏览器是否支持屏幕共享
  342. if (navigator.mediaDevices.getDisplayMedia) {
  343. navigator.mediaDevices
  344. .getDisplayMedia({ video: true })
  345. .then((stream) => {
  346. // 这里可以处理获取到的媒体流,例如播放视频等
  347. // 为了简化代码,这里只是打印流信息
  348. console.log("Screen sharing started:", stream);
  349. })
  350. .catch((error) => {
  351. console.error("Screen sharing error:", error);
  352. });
  353. } else {
  354. alert("Screen sharing not supported by this browser");
  355. }
  356. },
  357. // 获取视频基本信息
  358. getVideoBasicInfo() {
  359. return new Promise((resolve, reject) => {
  360. let vm = this;
  361. let query = {
  362. videoUrl: vm.form.url,
  363. };
  364. $req.request({ alias: "view-records-get", query })
  365. .then(async (res) => {
  366. if (res.code == 0) {
  367. vm.form.page = Number(res.data.currentSet);
  368. vm.form.second = res.data.duration;
  369. vm.collectState = res.data.isFavorite;
  370. resolve();
  371. } else {
  372. await vm.recordVideos();
  373. resolve();
  374. }
  375. })
  376. .catch(() => {
  377. resolve();
  378. });
  379. });
  380. },
  381. //收藏||取消收藏
  382. setCollect() {
  383. let vm = this;
  384. let query = {
  385. videoUrl: vm.form.url,
  386. };
  387. let url = vm.collectState ? "favorite-cancel" : "favorite-add";
  388. $req.request({ alias: url, query })
  389. .then((res) => {
  390. if (res.code == 0) {
  391. vm.collectState = !vm.collectState;
  392. uni.showToast({
  393. title: vm.collectState ? "已收藏" : "已取消",
  394. icon: "success",
  395. duration: 2000,
  396. });
  397. }
  398. })
  399. .catch(() => {});
  400. },
  401. //记录视频
  402. recordVideos() {
  403. let vm = this;
  404. console.log(vm.form, "vm.form");
  405. return new Promise((resolve, reject) => {
  406. let body = {
  407. videoUrl: vm.form.url,
  408. name: vm.form.title,
  409. poster: vm.form.poster,
  410. duration: vm.currentTime,
  411. currentSet: vm.form.page,
  412. };
  413. $req.request({ alias: "view-records-add", body })
  414. .then((res) => {
  415. if (res.code == 0) {
  416. resolve();
  417. } else {
  418. // vm.recordVideos();
  419. }
  420. })
  421. .catch(() => {
  422. // vm.recordVideos();
  423. });
  424. });
  425. },
  426. // 返回上一页
  427. async goBack() {
  428. let vm = this;
  429. // uni.showLoading();
  430. vm.recordVideos();
  431. uni.navigateBack({
  432. url: "/pages/index/index",
  433. });
  434. // uni.hideLoading();
  435. },
  436. // 播放上一个视频
  437. prevBtnClick() {
  438. let vm = this;
  439. vm.form.page--;
  440. vm.getform(vm.videoCollectionList[vm.form.page].url);
  441. },
  442. //播放下一个视频
  443. onNextBtnclick() {
  444. let vm = this;
  445. vm.form.page++;
  446. vm.getform(vm.videoCollectionList[vm.form.page].url);
  447. },
  448. timeupdate(data) {
  449. let num = data.detail.currentTime;
  450. this.currentTime = Math.round(num);
  451. },
  452. // 播放视频
  453. goPlay() {
  454. let vm = this;
  455. vm.$refs.yingbingVideo.play();
  456. },
  457. // 获取视频详情
  458. getVideoDetail() {
  459. return new Promise((resolve, reject) => {
  460. let vm = this;
  461. uni.request({
  462. url: vm.form.url, // 接口地址
  463. method: "GET", // 请求方法,可选值:GET、POST等
  464. success: (res) => {
  465. let parser = new DOMParser();
  466. let doc = parser.parseFromString(res.data, "text/html");
  467. //获取标题
  468. vm.form.title = doc.querySelector(
  469. ".detail-title>strong"
  470. ).innerHTML;
  471. //简介
  472. vm.form.content =
  473. doc.querySelector(".detail-desc>p").innerHTML;
  474. let detailRowEl =
  475. doc.querySelectorAll(".detail-info-row");
  476. detailRowEl.forEach((item) => {
  477. if (
  478. item.querySelector(".detail-info-row-side")
  479. .innerHTML == "备注:"
  480. ) {
  481. vm.form.plan = item.querySelector(
  482. ".detail-info-row-main"
  483. ).innerHTML;
  484. }
  485. });
  486. vm.form.poster =
  487. "https://61.147.93.252:15002" +
  488. doc
  489. .querySelector(".detail-pic>img")
  490. .getAttribute("data-original");
  491. //获取播放视频源-默认选第一个
  492. let episodeEl = doc.querySelectorAll(".episode-list");
  493. let episodeListEl =
  494. episodeEl[0].querySelectorAll(".episode-item");
  495. episodeListEl.forEach((a) => {
  496. // let name = /\d/.test(a.innerHTML)
  497. // ? a.innerHTML.match(/\d+/g).map(Number)[0]
  498. // : a.innerHTML;
  499. vm.videoCollectionList.push({
  500. name: a.innerHTML,
  501. url:
  502. "https://www.keke1.app" +
  503. a.getAttribute("href"),
  504. });
  505. });
  506. vm.recommendVideoList = listPackage(
  507. res.data
  508. )[0].children;
  509. resolve();
  510. },
  511. fail: (err) => {
  512. resolve();
  513. },
  514. });
  515. });
  516. },
  517. // 获取播放信息
  518. getform(url) {
  519. let vm = this;
  520. return new Promise((resolve, reject) => {
  521. uni.request({
  522. url: url, // 接口地址
  523. method: "GET", // 请求方法,可选值:GET、POST等
  524. success: (res) => {
  525. vm.form.playVideoUrl = res.data
  526. .split("function initVideoPlayer() {")[1]
  527. .split("src")[1]
  528. .split(",")[0]
  529. .split('"')[1];
  530. // vm.$nextTick(() => {
  531. // vm.$refs.sunnyVideo.changePlay();
  532. // });
  533. resolve();
  534. },
  535. fail: (err) => {
  536. resolve();
  537. },
  538. });
  539. });
  540. },
  541. async setEpisode(item, index) {
  542. let vm = this;
  543. vm.$refs.sunnyVideo.closeTrialEnd();
  544. vm.form.page = index;
  545. vm.getform(item.url);
  546. },
  547. goPlayVideo(row) {
  548. uni.reLaunch({
  549. url: "/pages/play/play?url=" + row.url,
  550. });
  551. },
  552. },
  553. };
  554. </script>
  555. <style lang="less" scoped>
  556. .video-page-wrap {
  557. overflow: hidden;
  558. flex: 1;
  559. .player {
  560. width: 100%;
  561. height: 480rpx;
  562. // background-color: #000;
  563. .back {
  564. position: absolute;
  565. top: 10px;
  566. left: 8px;
  567. z-index: 999;
  568. }
  569. .icon-play {
  570. position: absolute;
  571. width: 100%;
  572. height: 100%;
  573. display: flex;
  574. align-items: center;
  575. justify-content: center;
  576. // opacity: 1;
  577. img {
  578. width: 120rpx;
  579. height: 120rpx;
  580. }
  581. }
  582. .time-text {
  583. position: absolute;
  584. width: 100%;
  585. height: 100%;
  586. display: flex;
  587. align-items: center;
  588. justify-content: center;
  589. color: #fff;
  590. font-size: 48rpx;
  591. }
  592. }
  593. .main-content {
  594. flex: 1;
  595. overflow-x: hidden;
  596. margin-top: 36rpx;
  597. padding: 0 36rpx 30rpx;
  598. .title {
  599. color: #000;
  600. font-size: 32rpx;
  601. font-weight: bold;
  602. }
  603. .text-but {
  604. width: 100rpx;
  605. height: 50rpx;
  606. background-color: #f7f7f7;
  607. border-radius: 10rpx 10rpx 10rpx 10rpx;
  608. text-align: center;
  609. }
  610. .describe {
  611. color: #a6a6a6;
  612. font-size: 24rpx;
  613. margin-top: 30rpx;
  614. line-height: 1.5;
  615. }
  616. .handle-box {
  617. padding: 28rpx 38rpx 34rpx;
  618. // height: 160rpx;
  619. background-color: #f7f7f7;
  620. border-radius: 14rpx;
  621. margin-top: 60rpx;
  622. display: flex;
  623. justify-content: space-around;
  624. align-items: center;
  625. .handle-row {
  626. // width: 140rpx;
  627. img {
  628. width: 60rpx;
  629. height: 60rpx;
  630. }
  631. }
  632. .handle-text {
  633. font-size: 28rpx;
  634. font-weight: 500;
  635. color: #464646;
  636. margin-top: 5rpx;
  637. }
  638. }
  639. text {
  640. font-size: 24rpx;
  641. color: #717171;
  642. margin-right: 4rpx;
  643. font-weight: bold;
  644. }
  645. }
  646. .gather_but {
  647. margin: 10rpx;
  648. min-width: 11.4%;
  649. height: 68rpx;
  650. padding: 16rpx 20rpx;
  651. border-radius: 8rpx;
  652. background-color: #f7f7f7;
  653. }
  654. }
  655. /deep/.u-popup {
  656. flex: unset;
  657. }
  658. .tbbox {
  659. height: 500rpx;
  660. padding-top: 30rpx;
  661. .tptitle {
  662. position: absolute;
  663. top: 18rpx;
  664. left: 30rpx;
  665. .tptitletext {
  666. font-size: 28rpx;
  667. font-weight: bold;
  668. }
  669. }
  670. .tbitem {
  671. padding: 30rpx;
  672. flex-direction: row;
  673. align-items: center;
  674. .tbitemtext {
  675. font-size: 28rpx;
  676. font-weight: bold;
  677. }
  678. }
  679. .tbbtn {
  680. position: absolute;
  681. bottom: 20rpx;
  682. left: 225rpx;
  683. width: 300rpx;
  684. }
  685. }
  686. .poster {
  687. display: flex;
  688. align-items: center;
  689. justify-content: center;
  690. height: 100%;
  691. width: 100%;
  692. img {
  693. width: 600rpx;
  694. }
  695. .icon-close {
  696. position: absolute;
  697. top: 60rpx;
  698. right: 40rpx;
  699. z-index: 1;
  700. }
  701. }
  702. </style>