MediaPlayer.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <template>
  2. <div v-bind:class="classes" v-on:click="handleClick">
  3. <div style="overflow: 'hidden'; text-overflow: 'ellipsis';">用户ID: {{stream.uid}}</div>
  4. <div style="overflow: 'hidden'; text-overflow: 'ellipsis';">流ID: {{stream.sid}}</div>
  5. <div v-show="stream.mediaStream" style="overflow: 'hidden'; text-overflow: 'ellipsis';">音量: {{volume}} % &nbsp;&nbsp;&nbsp;&nbsp;音频丢包率: {{stats.audioLost}} %</div>
  6. <div v-show="stream.mediaStream" style="overflow: 'hidden'; text-overflow: 'ellipsis';">视频丢包率: {{stats.videoLost}} % &nbsp;&nbsp;&nbsp;&nbsp;网络延时: {{stats.rtt}} ms</div>
  7. <div class="video-container" v-show="stream.mediaStream">
  8. <video
  9. ref="video"
  10. webkit-playsinline
  11. autoplay
  12. playsinline
  13. v-bind:muted="isIOS">
  14. </video>
  15. <div class="muted-mask" v-show="isMuted">
  16. <div class="mask-content">
  17. <div class="hint">由于iOS系统限制,视频自动播放时需要静音,请点击下面的按钮来取消静音</div>
  18. <button @click.stop="handleUnmute">取消静音</button>
  19. </div>
  20. </div>
  21. </div>
  22. <p v-show="!stream.mediaStream">unsubscribe</p>
  23. </div>
  24. </template>
  25. <script>
  26. import classnames from 'unique-classnames';
  27. export default {
  28. name: 'MediaPlayer',
  29. data: function () {
  30. const classes = classnames('media-player', this.className);
  31. function isIOS () {
  32. return /.*iphone.*/i.test(navigator.userAgent);
  33. }
  34. return {
  35. classes: classes,
  36. volume: 0,
  37. stats: {
  38. audioLost: 0,
  39. biggestAudioLost: 0,
  40. videoLost: 0,
  41. biggestVideoLost: 0,
  42. rtt: 0,
  43. biggestRTT: 0
  44. },
  45. isIOS: isIOS(),
  46. isMuted: isIOS()
  47. };
  48. },
  49. props: {
  50. className: {
  51. type: String,
  52. default: ''
  53. },
  54. stream: {
  55. type: Object,
  56. default: function () {
  57. return {};
  58. }
  59. },
  60. client: {
  61. type: Object,
  62. default: function () {
  63. return null;
  64. }
  65. },
  66. onClick: {
  67. type: Function,
  68. default: function () {}
  69. }
  70. },
  71. created: function () {
  72. this.volumeTimer = 0;
  73. this.stateTimer = 0;
  74. },
  75. mounted: function () {
  76. this.isComponentDestroyed = false;
  77. if (this.stream.mediaStream) {
  78. this.play(this.stream.mediaStream);
  79. }
  80. },
  81. beforeDestroy: function () {
  82. this.stop();
  83. },
  84. destroyed: function () {
  85. this.isComponentDestroyed = true;
  86. },
  87. watch: {
  88. 'stream.mediaStream': function (val, oldVal) {
  89. console.log('media stream changed: ', val, oldVal);
  90. if (val) {
  91. this.play(val);
  92. } else {
  93. this.stop();
  94. }
  95. }
  96. },
  97. methods: {
  98. play: function (mediaStream) {
  99. this.$refs.video.srcObject = mediaStream;
  100. this.startGetVolume();
  101. this.startGetState();
  102. },
  103. stop: function () {
  104. this.stopGetVolume();
  105. this.stopGetState();
  106. this.$refs.video.srcObject = null;
  107. },
  108. startGetVolume: function () {
  109. const { client, stream } = this;
  110. if (!client || !stream || !stream.audio) {
  111. return;
  112. }
  113. if (this.volumeTimer) {
  114. clearInterval(this.volumeTimer);
  115. }
  116. this.volumeTimer = setInterval(() => {
  117. this.volume = client.getAudioVolume(stream.sid);
  118. }, 1000);
  119. },
  120. stopGetVolume: function () {
  121. clearInterval(this.volumeTimer);
  122. },
  123. startGetState: function () {
  124. const { client, stream } = this;
  125. if (!client || !stream || !stream.video) {
  126. return;
  127. }
  128. if (this.stateTimer) {
  129. clearInterval(this.stateTimer);
  130. }
  131. this.stateTimer = setInterval(() => {
  132. client.getAudioStats(stream.sid, (_stats) => {
  133. if (this.isComponentDestroyed) return;
  134. const { stats } = this;
  135. stats.audioLost = _stats.lostpre;
  136. if (stats.biggestAudioLost < _stats.lostpre) {
  137. stats.biggestAudioLost = _stats.lostpre;
  138. }
  139. }, (e) => {
  140. console.error('get video stats ', stream.sid);
  141. });
  142. client.getVideoStats(stream.sid, (_stats) => {
  143. if (this.isComponentDestroyed) return;
  144. const { stats } = this;
  145. stats.videoLost = _stats.lostpre;
  146. if (stats.biggestVideoLost < _stats.lostpre) {
  147. stats.biggestVideoLost = _stats.lostpre;
  148. }
  149. }, (e) => {
  150. console.error('get video stats ', stream.sid);
  151. });
  152. client.getNetworkStats(stream.sid, (_stats) => {
  153. if (this.isComponentDestroyed) return;
  154. const { stats } = this;
  155. stats.rtt = _stats.rtt;
  156. if (stats.biggestRTT < _stats.rtt) {
  157. stats.biggestRTT = _stats.rtt;
  158. }
  159. }, (e) => {
  160. console.error('get network stats ', stream.sid);
  161. });
  162. }, 1000);
  163. },
  164. stopGetState: function () {
  165. clearInterval(this.stateTimer);
  166. },
  167. handleClick: function () {
  168. const { stream, onClick } = this;
  169. onClick && onClick(stream);
  170. },
  171. handleUnmute: function () {
  172. if (this.$refs.video) {
  173. this.$refs.video.muted = false;
  174. this.isMuted = false;
  175. }
  176. }
  177. }
  178. };
  179. </script>
  180. <!-- Add "scoped" attribute to limit CSS to this component only -->
  181. <style scoped>
  182. .media-player {
  183. display: inline-block;
  184. margin: 2px;
  185. width: 300px;
  186. text-align: left;
  187. white-space: nowrap;
  188. cursor: pointer;
  189. }
  190. .media-player .video-container {
  191. position: relative;
  192. }
  193. .media-player .muted-mask {
  194. position: absolute;
  195. top: 0;
  196. right: 0;
  197. bottom: 0;
  198. left: 0;
  199. z-index: 9;
  200. opacity: 0.6;
  201. background-color: #fff;
  202. display: flex;
  203. flex-direction: column;
  204. justify-content: center;
  205. align-items: center;
  206. }
  207. .muted-mask .mask-content {
  208. max-width: 200px;
  209. }
  210. .mask-content .hint {
  211. margin-bottom: 12px;
  212. white-space: break-spaces;
  213. font-size: 14px;
  214. }
  215. .mask-content button {
  216. min-height: 50px;
  217. }
  218. .media-player video {
  219. width: 100%;
  220. height: 100%;
  221. }
  222. </style>