VideoPlayer.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <div>
  3. <div v-if="status.sensitive == true" class="content-label-wrapper">
  4. <div class="text-light content-label">
  5. <p class="text-center">
  6. <i class="far fa-eye-slash fa-2x"></i>
  7. </p>
  8. <p class="h4 font-weight-bold text-center">
  9. Sensitive Content
  10. </p>
  11. <p class="text-center py-2 content-label-text">
  12. {{ status.spoiler_text ? status.spoiler_text : 'This post may contain sensitive content.'}}
  13. </p>
  14. <p class="mb-0">
  15. <button @click="status.sensitive = false" class="btn btn-outline-light btn-block btn-sm font-weight-bold">See Post</button>
  16. </p>
  17. </div>
  18. </div>
  19. <template v-else>
  20. <div v-if="!shouldPlay" class="content-label-wrapper" :style="{ background: `linear-gradient(rgba(0, 0, 0, 0.2),rgba(0, 0, 0, 0.8)),url(${getPoster(status)})`, backgroundSize: 'cover'}">
  21. <div class="text-light content-label">
  22. <p class="mb-0">
  23. <button @click.prevent="handleShouldPlay" class="btn btn-link btn-block btn-sm font-weight-bold">
  24. <i class="fas fa-play fa-5x text-white"></i>
  25. </button>
  26. </p>
  27. </div>
  28. </div>
  29. <template v-else>
  30. <video v-if="hasHls" ref="video" :class="{ fixedHeight: fixedHeight }" style="margin:0" playsinline controls autoplay="false" :poster="getPoster(status)">
  31. </video>
  32. <video v-else class="card-img-top shadow" :class="{ fixedHeight: fixedHeight }" style="border-radius:15px;object-fit: contain;background-color: #000;" autoplay="false" controls :poster="getPoster(status)">
  33. <source :src="status.media_attachments[0].url" :type="status.media_attachments[0].mime">
  34. </video>
  35. </template>
  36. </template>
  37. </div>
  38. </template>
  39. <script type="text/javascript">
  40. import Hls from 'hls.js';
  41. import "plyr/dist/plyr.css";
  42. import Plyr from 'plyr';
  43. import { p2pml } from '@peertube/p2p-media-loader-core'
  44. import { Engine, initHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs'
  45. export default {
  46. props: ['status', 'fixedHeight'],
  47. data() {
  48. return {
  49. shouldPlay: false,
  50. hasHls: undefined,
  51. hlsConfig: window.App.config.features.hls,
  52. liveSyncDurationCount: 7,
  53. isHlsSupported: false,
  54. isP2PSupported: false,
  55. engine: undefined,
  56. }
  57. },
  58. mounted() {
  59. this.$nextTick(() => {
  60. this.init();
  61. })
  62. },
  63. methods: {
  64. handleShouldPlay(){
  65. this.shouldPlay = true;
  66. this.isHlsSupported = this.hlsConfig.enabled && Hls.isSupported();
  67. this.isP2PSupported = this.hlsConfig.enabled && this.hlsConfig.p2p && Engine.isSupported();
  68. this.$nextTick(() => {
  69. this.init();
  70. })
  71. },
  72. init() {
  73. if(!this.status.sensitive && this.status.media_attachments[0]?.hls_manifest && this.isHlsSupported) {
  74. this.hasHls = true;
  75. this.$nextTick(() => {
  76. this.initHls();
  77. })
  78. } else {
  79. this.hasHls = false;
  80. }
  81. },
  82. initHls() {
  83. let loader;
  84. if(this.isP2PSupported) {
  85. const config = {
  86. loader: {
  87. trackerAnnounce: [this.hlsConfig.tracker],
  88. rtcConfig: {
  89. iceServers: [
  90. {
  91. urls: [this.hlsConfig.ice]
  92. }
  93. ],
  94. }
  95. }
  96. };
  97. var engine = new Engine(config);
  98. if(this.hlsConfig.p2p_debug) {
  99. engine.on("peer_connect", peer => console.log("peer_connect", peer.id, peer.remoteAddress));
  100. engine.on("peer_close", peerId => console.log("peer_close", peerId));
  101. engine.on("segment_loaded", (segment, peerId) => console.log("segment_loaded from", peerId ? `peer ${peerId}` : "HTTP", segment.url));
  102. }
  103. loader = engine.createLoaderClass();
  104. } else {
  105. loader = Hls.DefaultConfig.loader;
  106. }
  107. const video = this.$refs.video;
  108. const source = this.status.media_attachments[0].hls_manifest;
  109. const player = new Plyr(video, {
  110. captions: {
  111. active: true,
  112. update: true,
  113. },
  114. });
  115. const hls = new Hls({
  116. liveSyncDurationCount: this.liveSyncDurationCount,
  117. loader: loader,
  118. });
  119. let self = this;
  120. initHlsJsPlayer(hls);
  121. hls.loadSource(source);
  122. hls.attachMedia(video);
  123. hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
  124. if(this.hlsConfig.debug) {
  125. console.log(event);
  126. console.log(data);
  127. }
  128. const defaultOptions = {};
  129. const availableQualities = hls.levels.map((l) => l.height)
  130. if(this.hlsConfig.debug) {
  131. console.log(availableQualities);
  132. }
  133. availableQualities.unshift(0);
  134. defaultOptions.quality = {
  135. default: 0,
  136. options: availableQualities,
  137. forced: true,
  138. onChange: (e) => self.updateQuality(e),
  139. }
  140. defaultOptions.i18n = {
  141. qualityLabel: {
  142. 0: 'Auto',
  143. },
  144. }
  145. hls.on(Hls.Events.LEVEL_SWITCHED, function(event, data) {
  146. var span = document.querySelector(".plyr__menu__container [data-plyr='quality'][value='0'] span")
  147. if (hls.autoLevelEnabled) {
  148. span.innerHTML = `Auto (${hls.levels[data.level].height}p)`
  149. } else {
  150. span.innerHTML = `Auto`
  151. }
  152. })
  153. var player = new Plyr(video, defaultOptions);
  154. });
  155. },
  156. updateQuality(newQuality) {
  157. if (newQuality === 0) {
  158. window.hls.currentLevel = -1;
  159. } else {
  160. window.hls.levels.forEach((level, levelIndex) => {
  161. if (level.height === newQuality) {
  162. if(this.hlsConfig.debug) {
  163. console.log("Found quality match with " + newQuality);
  164. }
  165. window.hls.currentLevel = levelIndex;
  166. }
  167. });
  168. }
  169. },
  170. getPoster(status) {
  171. let url = status.media_attachments[0].preview_url;
  172. if(url.endsWith('no-preview.jpg') || url.endsWith('no-preview.png')) {
  173. return;
  174. }
  175. return url;
  176. }
  177. }
  178. }
  179. </script>