import '../assets/css/player.scss' ; import Template from '@/core/template' ; import Events from '@/core/events' ; import { Format , base64ToBlob } from '@/utils/utils' ; import Hls from 'hls.js' ; import dashjs from 'dashjs' ; import LoPlayerConfig , { LoPlayerOptions } from '@/config/index' ; type PanelOptions = { [ propName in number | string ] : any ; } ; export default class LoPlayer { el : HTMLElement ; options : LoPlayerOptions ; playStatus : boolean ; loading : boolean ; player ! : HTMLVideoElement ; currentTime : number ; duration : number ; volumeIcon : string ; currentIndex : number ; speed : number [ ] ; switch : boolean ; getEl : any ; events ? : Events ; constructor ( el , options = LoPlayerConfig ) { this . el = el ; this . options = options ; this . playStatus = false ; this . loading = true ; this . currentTime = 0 ; this . duration = 0 ; this . volumeIcon = '' ; this . currentIndex = 0 ; this . speed = this . options . speed ?? [ 0.25 , 0.5 , 1 , 1.25 , 1.5 , 1.75 , 2 ] ; this . switch = typeof this . options . src !== 'string' ; this . getEl = new Template ( { container : this . el , switch : this . switch , screenShot : this . options . screenShot , speed : this . speed , currentIndex : this . currentIndex , currentTime : Format ( this . currentTime ) } ) ; this . init ( ) ; } init ( ) { console . log ( this ) ; if ( ! this . el ) { throw new Error ( this . el + ' is not found' ) ; } else { const { player , prevBtn , nextBtn } = this . getEl ; this . player = player ; this . events = new Events ( this . player ) ; this . showLoading ( this . loading ) ; this . bindEvent ( ) ; this . mediaSourceExtensions ( ) ; this . canplay ( ) ; this . volumeChangeIcon ( ) ; this . timeupdate ( ) ; this . jump ( ) ; this . ended ( ) ; this . shortcutKey ( ) ; this . progressMove ( ) ; this . mouseUp ( ) ; this . volume ( ) ; this . volumeMove ( ) ; this . prev ( ) ; this . next ( ) ; this . setSpeed ( ) ; this . showSetting ( ) ; this . contextmenu ( ) ; this . error ( ) ; this . autoPlay ( ) ; if ( typeof this . options . src !== 'string' ) { if ( this . currentIndex === 0 ) { prevBtn . style . cssText = 'color: rgba(255, 255, 255, 0.7)' ; nextBtn . style . cssText = 'color: white' ; } if ( this . currentIndex === this . options . src . length - 1 ) { nextBtn . style . cssText = 'color: rgba(255, 255, 255, 0.7)' ; } } if ( this . options . loop ) this . setLoop ( true ) ; } } setLoop ( loop ) { if ( loop ) { this . player . setAttribute ( 'loop' , 'loop' ) ; } else { this . player . removeAttribute ( 'loop' ) ; } } bindEvent ( ) { const { playBtn , volumeBtn , fullScreen , screenshot , logo } = this . getEl ; playBtn . addEventListener ( 'click' , ( ) => { this . toggle ( ) ; } ) ; logo . addEventListener ( 'click' , ( ) => { this . play ( ) ; } ) ; volumeBtn . addEventListener ( 'click' , ( ) => { this . muted ( ) ; } ) ; fullScreen . addEventListener ( 'click' , ( ) => { this . fullScreen ( ) ; } ) ; if ( screenshot ) { screenshot . addEventListener ( 'click' , ( ) => { console . log ( 1 ) ; this . screenshot ( ) ; } ) ; } } contextmenu ( ) { this . player . addEventListener ( 'contextmenu' , e => { e . preventDefault ( ) ; } ) ; } autoPlay ( ) { if ( this . options . autoPlay ) { this . play ( ) ; } } screenshot ( filename = 'download' ) { const canvas = document . createElement ( 'canvas' ) ; canvas . width = this . player . offsetWidth ; canvas . height = this . player . offsetHeight ; const link = document . createElement ( 'a' ) ; const ctx = canvas . getContext ( '2d' ) ; document . body . appendChild ( canvas ) ; ctx ! . drawImage ( this . player , 0 , 0 , canvas . width , canvas . height ) ; const res = canvas . toDataURL ( 'image/png' ) ; const blob = base64ToBlob ( res ) ; const blobURL = URL . createObjectURL ( blob ) ; link . setAttribute ( 'href' , blobURL ) ; link . setAttribute ( 'download' , filename ) ; document . body . appendChild ( link ) ; link . click ( ) ; } error ( ) { this . player . addEventListener ( 'error' , ( e ) => { console . log ( e ) ; console . log ( this . player ) ; } ) ; } getExt ( str ) { if ( typeof str !== 'string' ) throw TypeError ( `${ str } is not a string` ) ; const start = str . lastIndexOf ( '.' ) ; if ( start !== - 1 ) { const ext = str . substr ( start + 1 , str . length ) ; return ext ; } } mediaSourceExtensions ( ) { this . player . load ( ) ; const playerURL = typeof this . options . src === 'string' ? this . options . src : this . options . src [ this . currentIndex ] . src ; const ext = this . getExt ( playerURL ) ; this . player . src = playerURL ; switch ( ext ) { case 'm3u8' : console . log ( 'm3u8' ) ; if ( Hls ) { if ( Hls . isSupported ( ) ) { const hls = new Hls ( ) ; hls . loadSource ( playerURL ) ; hls . attachMedia ( this . player ) ; console . log ( hls ) ; hls . on ( Hls . Events . MANIFEST_PARSED , ( ) => { this . play ( ) ; } ) ; hls . on ( Hls . Events . BUFFER_RESET , ( ) => { console . log ( 'test' ) ; } ) ; } else if ( this . player . canPlayType ( 'application/vnd.apple.mpegurl' ) ) { this . player . src = playerURL ; } } break ; case 'mpd' : console . log ( 'dash' ) ; if ( dashjs ) { const player = dashjs . MediaPlayer ( ) . create ( ) ; player . initialize ( this . player , playerURL , false ) ; player . reset ( ) ; } break ; default : console . log ( `当前视频格式为：${ ext }` ) ; this . player . load ( ) ; this . player . src = playerURL ; break ; } } shortcutKey ( ) { window . onkeydown = ( ev ) => { /**
             * 32 空格播放暂停
             * 70 F 进入和退出全屏
             * 77 M 调整是否静音
             */ switch ( ev . keyCode ) { case 27 : this . options . fullScreen = false ; break ; case 32 : this . toggle ( ) ; break ; case 70 : this . fullScreen ( ) ; break ; case 77 : this . muted ( ) ; break ; case 122 : break ; default : console . log ( '无效按键：' + ev . keyCode ) ; break ; } } ; } preload ( ) { const { videoProgressLine , preload } = this . getEl ; if ( this . player && this . player . buffered ) { const len = this . player . buffered . length - 1 ; if ( len >= 0 && len < 2 ) { const end = this . player . buffered . end ( len ) ; const position = ( end / this . player . duration ) * videoProgressLine . offsetWidth ; preload . style . width = position . toFixed ( 2 ) + 'px' ; } } } canplay ( ) { const { duration } = this . getEl ; this . player . addEventListener ( 'canplay' , ( ) => { duration . innerHTML = Format ( this . player . duration ) ; this . loading = false ; this . showLoading ( this . loading ) ; this . preload ( ) ; this . showLogo ( ) ; } ) ; } toggle ( ) { this . playStatus = ! this . playStatus ; this . playStatus ? this . play ( ) : this . pause ( ) ; } play ( ) { const { playBtn } = this . getEl ; this . playStatus = true ; this . player . play ( ) ; this . showLogo ( this . playStatus ) ; playBtn . className = 'icon iconfont icon-tingzhi' ; } pause ( ) { const { playBtn } = this . getEl ; this . playStatus = false ; this . player . pause ( ) ; this . showLogo ( this . playStatus ) ; playBtn . className = 'icon iconfont icon-caret-right' ; } prev ( ) { const { prevBtn , nextBtn } = this . getEl ; if ( prevBtn ) { prevBtn . addEventListener ( 'click' , ( ) => { if ( this . currentIndex > 0 ) { this . player . setAttribute ( 'preload' , 'none' ) ; -- this . currentIndex ; this . changeVideo ( ) ; this . play ( ) ; if ( this . currentIndex === 0 ) { prevBtn . style . cssText = 'color: rgba(255, 255, 255, 0.7)' ; nextBtn . style . cssText = 'color: white' ; return ; } } } ) ; } } next ( ) { const { nextBtn , prevBtn } = this . getEl ; if ( nextBtn ) { nextBtn . addEventListener ( 'click' , ( ) => { if ( this . currentIndex < this . options . src . length - 1 ) { this . player . setAttribute ( 'preload' , 'none' ) ; ++ this . currentIndex ; this . changeVideo ( ) ; this . play ( ) ; prevBtn . style . cssText = 'color: white' ; if ( this . currentIndex === this . options . src . length - 1 ) { nextBtn . style . cssText = 'color: rgba(255, 255, 255, 0.7)' ; return ; } } } ) ; } } changeVideo ( ) { const { currentTime , duration , preload , videoProgressBar , videoProgress } = this . getEl ; this . pause ( ) ; this . showLoading ( true ) ; this . mediaSourceExtensions ( ) ; console . log ( this . player ) ; this . currentTime = 0 ; this . duration = 0 ; duration . innerHTML = Format ( this . duration ) ; currentTime . innerHTML = Format ( this . currentTime ) ; videoProgress . style . width = 0 + 'px' ; videoProgressBar . style . left = 0 + 'px' ; preload . style . width = 0 + 'px' ; this . play ( ) ; } timeupdate ( ) { const { currentTime , videoProgressLine , videoProgressBar , videoProgress } = this . getEl ; const videoProgressLineWidth = videoProgressLine . offsetWidth ; const videoProgressBarWidth = videoProgressBar . offsetWidth ; this . player . addEventListener ( 'timeupdate' , ( ) => { this . preload ( ) ; this . currentTime = this . player . currentTime ; currentTime . innerHTML = Format ( this . player . currentTime ) ; const videoProgressBarLeft = videoProgressBar . offsetLeft ; const position = ( this . player . currentTime / this . player . duration ) * videoProgressLineWidth ; let max = position - 1 ; if ( videoProgressBarLeft >= ( videoProgressLineWidth - videoProgressBarWidth ) ) { max = videoProgressLineWidth - videoProgressBarWidth ; } if ( Math . floor ( position ) === 0 ) max = 0 ; currentTime . innerHTML = Format ( this . player . currentTime ) ; videoProgress . style . width = position + 'px' ; videoProgressBar . style . left = max + 'px' ; const readyState = this . player . readyState ; const networkState = this . player . networkState ; if ( readyState === 4 && ( networkState === 1 || networkState === 2 ) ) { this . showLoading ( false ) ; } else { this . showLoading ( true ) ; } } ) ; } seekTo ( time ) { this . player . currentTime = time ; this . currentTime = time ; const { videoProgress , videoProgressLine , videoProgressBar } = this . getEl ; const position = ( this . player . currentTime / this . player . duration ) * videoProgressLine . offsetWidth ; videoProgress . style . width = position + 'px' ; videoProgressBar . style . left = position + 'px' ; } jump ( ) { const { videoProgressLine } = this . getEl ; videoProgressLine . addEventListener ( 'click' , ( e ) => { const position = e . offsetX ; const currentTime = position / videoProgressLine . offsetWidth * this . player . duration ; this . seekTo ( currentTime ) ; this . preload ( ) ; } ) ; } progressMove ( ) { const { videoProgressLine , videoProgressBar , controlBox , playerBox } = this . getEl ; videoProgressBar . onmousedown = ( ) => { document . onmousemove = ( ev ) => { ev . preventDefault ( ) ; let position = ev . clientX - controlBox . offsetLeft - playerBox . offsetLeft ; const maxMovePoint = videoProgressLine . offsetWidth - videoProgressBar . offsetWidth ; if ( position > maxMovePoint ) { position = maxMovePoint ; } else if ( position < 0 ) { position = 0 ; } const currentTime = ( position / maxMovePoint ) * this . player . duration ; this . seekTo ( currentTime ) ; } ; } ; } mouseUp ( ) { document . onmouseup = ( ) => { document . onmousemove = null ; } ; } ended ( ) { const { playBtn } = this . getEl ; this . player . addEventListener ( 'ended' , ( ) => { this . playStatus = false ; playBtn . className = 'icon iconfont icon-caret-right' ; this . showLogo ( ) ; } ) ; } FullScreen ( ) { const { playerBox } = this . getEl ; const el = playerBox ; if ( el . requestFullscreen ) { el . requestFullscreen ( ) ; } else if ( el . mozRequestFullScreen ) { el . mozRequestFullScreen ( ) ; } else if ( el . webkitRequestFullScreen ) { el . webkitRequestFullScreen ( ) ; } } ExitFullscreen ( ) { document . exitFullscreen ( ) ; } fullScreen ( ) { const { fullScreen } = this . getEl ; let fullScreenIcon = '' ; this . options . fullScreen = ! this . options . fullScreen ; if ( this . options . fullScreen ) { this . FullScreen ( ) ; fullScreenIcon = 'icon-fullscreen-exit' ; } else { this . ExitFullscreen ( ) ; fullScreenIcon = 'icon-fullscreen' ; } fullScreen . classList . replace ( fullScreen . classList [ fullScreen . classList . length - 1 ] , fullScreenIcon ) ; } setSpeed ( ) { const { speedPanel } = this . getEl ; for ( let i = 0 ; i < this . speed . length ; i ++ ) { if ( speedPanel . children [ i ] . innerHTML === '1' ) { speedPanel . children [ i ] . className = 'panelActive' ; } else { speedPanel . children [ i ] . className = '' ; } speedPanel . children [ i ] . addEventListener ( 'click' , ( ) => { this . player . playbackRate = this . speed [ i ] ; for ( let j = 0 ; j < this . speed . length ; j ++ ) { if ( this . player . playbackRate === this . speed [ j ] ) { speedPanel . children [ j ] . className = 'panelActive' ; } else { speedPanel . children [ j ] . className = '' ; } } speedPanel . style . cssText = 'display: none;' ; } ) ; } } volumeChangeIcon ( ) { const { volumeBtn } = this . getEl ; const volume = this . player . volume ; if ( volume > 0.75 ) { this . volumeIcon = 'icon-yinliang' ; } else if ( volume > 0.4 && volume < 0.75 ) { this . volumeIcon = 'icon-yinliangdaxiao' ; } else if ( volume > 0 && volume < 0.4 ) { this . volumeIcon = 'icon-zuixiaoyinliang' ; } else if ( volume === 0 ) { this . volumeIcon = 'icon-guanbiyinliang' ; } volumeBtn . classList . replace ( volumeBtn . classList [ volumeBtn . classList . length - 1 ] , this . volumeIcon ) ; } volumeMove ( ) { const { volumeLine , volumeBar , volumeBox , playerBox } = this . getEl ; volumeBar . onmousedown = ( ) => { document . onmousemove = ( ev ) => { let position = ev . clientX - volumeBox . offsetLeft - playerBox . offsetLeft ; const maxMovePoint = volumeLine . offsetWidth - volumeBar . offsetWidth ; if ( position > maxMovePoint ) { position = maxMovePoint ; } else if ( position < 0 ) { position = 0 ; } const volumeSize = Number ( ( position / maxMovePoint ) . toFixed ( 2 ) ) ; this . setVolume ( volumeSize ) ; } ; } ; } setVolume ( volume ) { console . log ( volume ) ; this . player . volume = volume ; const { volumeLine , volumeBar , volumeProgress } = this . getEl ; const position = Number ( ( this . player . volume / 1 ) . toFixed ( 2 ) ) * volumeLine . offsetWidth ; volumeProgress . style . width = position + 'px' ; volumeBar . style . left = position - 1 + 'px' ; this . volumeChangeIcon ( ) ; } volume ( ) { const { volumeLine , volumeBar , volumeProgress } = this . getEl ; console . log ( volumeProgress ) ; volumeLine . addEventListener ( 'click' , ( e ) => { let position = e . offsetX ; console . log ( position ) ; if ( position <= 0 ) { position = 0 ; } else if ( position > ( volumeLine . offsetWidth - volumeBar . offsetWidth ) ) { position = volumeLine . offsetWidth - volumeBar . offsetWidth ; } const volumeSize = Number ( ( position / volumeLine . offsetWidth ) . toFixed ( 2 ) ) ; console . log ( position / volumeLine . offsetWidth ) ; this . setVolume ( volumeSize ) ; } ) ; } muted ( ) { const { volumeBtn } = this . getEl ; this . player . muted = ! this . player . muted ; this . player . muted ? this . volumeIcon = 'icon-guanbiyinliang' : this . volumeIcon = 'icon-yinliang' ; volumeBtn . classList . replace ( volumeBtn . classList [ volumeBtn . classList . length - 1 ] , this . volumeIcon ) ; } showLoading ( showLoading = this . loading ) { const { loading } = this . getEl ; loading . style . cssText = `display:${ showLoading ? 'flex' : 'none' }` ; } showSetting ( ) { const { setting , settingPanel , speedPanel } = this . getEl ; const panel = { 0 : { show : false , name : 'speedPanel' } } ; let showPanel = false ; setting . addEventListener ( 'click' , ( ) => { showPanel = ! showPanel ; settingPanel . style . cssText = `display: ${ showPanel ? 'block' : 'none' }` ; speedPanel . style . cssText = 'display: none;' ; for ( let i = 0 ; i < settingPanel . children . length ; i ++ ) { settingPanel . children [ i ] . addEventListener ( 'click' , ( ) => { showPanel = false ; panel [ i ] . show = true ; settingPanel . style . cssText = `display: ${ showPanel ? 'block' : 'none' }` ; this . getEl [ panel [ i ] . name ] . style . cssText = `display: ${ panel [ i ] . show ? 'block' : 'none' }` ; } ) ; } } ) ; } showLogo ( showLogo = this . playStatus ) { const { logo } = this . getEl ; logo . style . cssText = `display:${ ! showLogo ? 'flex' : 'none' }` ; } }