惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Y
Y Combinator Blog
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
I
InfoQ
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园 - 司徒正美
L
LangChain Blog

zodream梦想开源/个人编程日记

文件解析笔记-zodream梦想开源/个人编程日记 密码本开发笔记之读写与保存-zodream梦想开源/个人编程日记 SkiaSharp 把 pixel byte[] 转成 SKBitmap-zodream梦想开源/个人编程日记 nas 使用 Docker 安装 gogs-zodream梦想开源/个人编程日记 复制 android 手机中的文件到电脑-zodream梦想开源/个人编程日记 周报:寻找优质的周刊-zodream梦想开源/个人编程日记 开发日志:对Markdown的代码块新增引用来源支持-zodream梦想开源/个人编程日记 周报:怎么写技术类的教程文章-zodream梦想开源/个人编程日记 css display:flex 布局尺寸超出问题-zodream梦想开源/个人编程日记 周报:SEO优化的思考-zodream梦想开源/个人编程日记 Edge 浏览器不适用 Edge Image Viewer 打开图片 -zodream梦想开源/个人编程日记 SEO 学习笔记(一) 内容来源-zodream梦想开源/个人编程日记 PHP 实现双因素身份认证(2FA)-zodream梦想开源/个人编程日记 WPF MVVM 获取List 多选数据-zodream梦想开源/个人编程日记 Burp Suite 抓包-zodream梦想开源/个人编程日记 使用 indexnow 注意事项-zodream梦想开源/个人编程日记 Godot 使用字体图标 例如: Iconfont、FontAwesome-zodream梦想开源/个人编程日记 angular 15 对指定页面进行访问限制-zodream梦想开源/个人编程日记 CSS 使用 column-count 实现瀑布流出现内容分割的解决办法-zodream梦想开源/个人编程日记 input 确认按键事件在手机端不生效-zodream梦想开源/个人编程日记 C# 使用socket 进行通讯-zodream梦想开源/个人编程日记 Maui开发中Windows应用开启管理员权限-zodream梦想开源/个人编程日记 Maui 中自定义控件-zodream梦想开源/个人编程日记 angular 14 使用 ng-template 实现tree 结构显示-zodream梦想开源/个人编程日记 c# 动态安装和卸载dll-zodream梦想开源/个人编程日记 慎用 CompositionTarget.Rendering-zodream梦想开源/个人编程日记 c# 重写 c++ 程序笔记:数据初始化-zodream梦想开源/个人编程日记 源码编译 aseprite-zodream梦想开源/个人编程日记 记录一下字符串分隔split各语言之间的不同-zodream梦想开源/个人编程日记 c# Gzip解码无头内容-zodream梦想开源/个人编程日记
一个简单的HTML音视频播放器-zodream梦想开源/个人编程日记
zodream · 2021-04-05 · via zodream梦想开源/个人编程日记

一个简单的HTML音视频播放器

适用场景

本播放器适用场景: 播客类型的文章,需要一个播放器,但是不需要预加载资源文件的。可以尽可能的减少不必要的加载及访客流量浪费。

版本

本播放器有两个版本:

  1. 基于 Jquery 的版本。本文的源码属于这个版本。
  2. 基于Angular 11 的版本。源码见【Github】需要的自取。这个版本音频视频播放器是分开的,而且视频内置了自动区分 iframe 使用。

注意:播放器中的图标都是字体图标,所以只能参考修改。

音频播放器

视频播放器初始界面

视频播放器播放界面

代码


interface IPlayerOption {
    [key: string]: any;
    src: string;
    type?: 'audio' | 'video' | 'iframe'
}

;(function($: any) {
    const EVENT_TIME_UPDATE = 'timeupdate';
    const EVENT_PLAY = 'play';
    const EVENT_PAUSE = 'pause';
    const EVENT_ENDED = 'ended';
    const EVENT_VOLUME_UPDATE = 'volumeupdate';
    const EVENT_TAP_PLAY = 'tap_play';
    const EVENT_TAP_PAUSE = 'tap_pause';
    const EVENT_BOOT = 'boot';
    const EVENT_TAP_VOLUME = 'tap_volume';
    const EVENT_TAP_TIME = 'tap_time';
    const EVENT_ENTER_FULL_SCREEN = 'full_screen';
    const EVENT_EXIT_FULL_SCREEN = 'exit_full_screen';

    const screenFull = function() {
        const fnMap = [
            [
                'requestFullscreen',
                'exitFullscreen',
                'fullscreenElement',
                'fullscreenEnabled',
                'fullscreenchange',
                'fullscreenerror'
            ],
            // New WebKit
            [
                'webkitRequestFullscreen',
                'webkitExitFullscreen',
                'webkitFullscreenElement',
                'webkitFullscreenEnabled',
                'webkitfullscreenchange',
                'webkitfullscreenerror'

            ],
            // Old WebKit
            [
                'webkitRequestFullScreen',
                'webkitCancelFullScreen',
                'webkitCurrentFullScreenElement',
                'webkitCancelFullScreen',
                'webkitfullscreenchange',
                'webkitfullscreenerror'

            ],
            [
                'mozRequestFullScreen',
                'mozCancelFullScreen',
                'mozFullScreenElement',
                'mozFullScreenEnabled',
                'mozfullscreenchange',
                'mozfullscreenerror'
            ],
            [
                'msRequestFullscreen',
                'msExitFullscreen',
                'msFullscreenElement',
                'msFullscreenEnabled',
                'MSFullscreenChange',
                'MSFullscreenError'
            ]
        ];

        for (const item of fnMap) {
            if (item && item[1] in document) {
                return item;
            }
        }
        return false;
    }();

    class MediaPlayer {
        constructor(
            public element: JQuery,
            public options: IPlayerOption
        ) {
            if (!this.options.src) {
                return;
            }
            this.init();
            this.bindCustomEvent();
        }

        private playerElement: HTMLVideoElement|HTMLAudioElement;
        private playerBar: JQuery;
        private booted = false;
        private volumeLast = 100;
        private duration = 0;

        public on(event: string, callback: Function): this {
            this.options['on' + event] = callback;
            return this;
        }

        public hasEvent(event: string): boolean {
            return this.options.hasOwnProperty('on' + event);
        }

        public trigger(event: string, ... args: any[]) {
            let realEvent = 'on' + event;
            if (!this.hasEvent(event)) {
                return;
            }
            return this.options[realEvent].call(this, ...args);
        }

        private bindCustomEvent() {
            this.on(EVENT_BOOT, () => {
                if (this.booted) {
                    return;
                }
                if (this.options.type === 'audio') {
                    this.bindAudioEvent();
                    return;
                }
                if (this.options.type === 'iframe') {
                    this.videoFrame();
                    this.booted = true;
                    return;
                }
                this.videoPlayer();
                this.initBar(this.element.find('.player-bar'));
                this.bindVideoEvent();
            }).on(EVENT_TAP_PLAY, () => {
                this.playerElement.play();
            }).on(EVENT_TAP_PAUSE, () => {
                this.playerElement.pause();
            }).on(EVENT_TIME_UPDATE, (p: number, t: number) => {
                this.duration = t;
                this.playerBar.find('.time').text(this.formatMinute(p) + '/' + this.formatMinute(t));
                const progess = this.playerBar.find('.slider .progress');
                progess.attr('title', parseInt(p.toString()));
                progess.find('.progress-bar').css('width', p * 100 / t + '%');
            }).on(EVENT_PLAY, () => {
                this.playerBar.find('.icon .fa').addClass('fa-pause').removeClass('fa-play');
            }).on(EVENT_PAUSE, () => {
                this.playerBar.find('.icon .fa').removeClass('fa-pause').addClass('fa-play');
            }).on(EVENT_ENDED, () => {
                this.trigger(EVENT_PAUSE);
            }).on(EVENT_TAP_VOLUME, (v: number) => {
                if (!this.playerElement) {
                    return;
                }
                this.playerElement.volume = v / 100;
                this.trigger(EVENT_VOLUME_UPDATE, v);
            }).on(EVENT_VOLUME_UPDATE, (v: number) => {
                const progess = this.playerBar.find('.volume-slider .progress');
                progess.attr('title', parseInt(v.toString()));
                progess.find('.progress-bar').css('width', v + '%');
                let volumeCls = 'fa-volume-up';
                if (v <= 0) {
                    volumeCls = 'fa-volume-off';
                } else if (v < 60) {
                    volumeCls = 'fa-volume-down';
                }
                this.playerBar.find('.volume-icon .fa').attr('class', 'fa ' + volumeCls);
            }).on(EVENT_TAP_TIME, (p: number) => {
                if (!this.playerElement) {
                    return;
                }
                this.playerElement.currentTime = p;
            }).on(EVENT_EXIT_FULL_SCREEN, () => {
                this.playerBar.find('.full-icon .fa').attr('class', 'fa fa-expand');
                this.element.removeClass('player-full');
            }).on(EVENT_ENTER_FULL_SCREEN, () => {
                this.element.addClass('player-full');
                this.playerBar.find('.full-icon .fa').attr('class', 'fa fa-compress');
            });
        }

        private init() {
            if (this.options.type === 'audio') {
                this.initAudio();
                return;
            }
            this.initVideo();
        }

        private initAudio() {
            this.audioPlayer();
            this.initBar(this.element);
        }

        private initBar(bar: JQuery) {
            this.playerBar = bar;
            const that = this;
            bar.on('click', '.icon .fa', function() {
                if (!that.booted) {
                    that.trigger(EVENT_BOOT);
                }
                that.trigger($(this).hasClass('fa-play') ? EVENT_TAP_PLAY : EVENT_TAP_PAUSE);
            }).on('click', '.volume-icon .fa', function() {
                const $this = $(this);
                if ($this.hasClass('fa-volume-mute') || $this.hasClass('fa-volume-off')) {
                    that.trigger(EVENT_TAP_VOLUME, that.volumeLast);
                    return;
                }
                if (that.playerElement) {
                    that.volumeLast = that.playerElement.volume * 100;
                }
                that.trigger(EVENT_TAP_VOLUME, 0);
            }).on('click', '.slider .progress', function(event) {
                const $this = $(this);
                that.trigger(EVENT_TAP_TIME, (event.clientX - $this.offset().left) * that.duration / $this.width());
            }).on('click', '.volume-slider .progress', function(event) {
                const $this = $(this);
                that.trigger(EVENT_TAP_VOLUME, (event.clientX - $this.offset().left) * 100 / $this.width());
            }).on('click', '.full-icon .fa', function() {
                if (that.element.hasClass('player-full')) {
                    that.exitFullscreen();
                    return;
                }
                that.fullScreen();
            });
        }

        private initVideo() {
            this.videoMask();
            this.element.on('click', '.player-mask', () => {
                this.trigger(EVENT_BOOT);
                if (this.playerElement) {
                    this.trigger(EVENT_TAP_PLAY);
                }
            });
        }

        private bindAudioEvent() {
            if (this.booted) {
                return;
            }
            this.booted = true;
            this.playerElement = document.createElement('audio');
            this.playerElement.src = this.options.src;
            this.playerElement.addEventListener('timeupdate', () => {
                if (isNaN(this.playerElement.duration) || !isFinite(this.playerElement.duration) || this.playerElement.duration <= 0) {
                    this.trigger(EVENT_TIME_UPDATE, 0, 0);
                    return;
                }
                this.trigger(EVENT_TIME_UPDATE, this.playerElement.currentTime, this.playerElement.duration);
            });
            this.playerElement.addEventListener('ended', () => {
                this.trigger(EVENT_ENDED);
            });
            this.playerElement.addEventListener('pause', () => {
                this.trigger(EVENT_PAUSE);
            });
            this.playerElement.addEventListener('play', () => {
                this.trigger(EVENT_PLAY);
            });
            this.trigger(EVENT_VOLUME_UPDATE, this.playerElement.volume * 100);
        }

        private bindVideoEvent() {
            if (this.booted) {
                return;
            }
            this.booted = true;
            this.playerElement = this.element.find('.player-video')[0] as HTMLVideoElement;
            this.playerElement.addEventListener('timeupdate', () => {
                if (isNaN(this.playerElement.duration) || !isFinite(this.playerElement.duration) || this.playerElement.duration <= 0) {
                    this.trigger(EVENT_TIME_UPDATE, 0, 0);
                    return;
                }
                this.trigger(EVENT_TIME_UPDATE, this.playerElement.currentTime, this.playerElement.duration);
            });
            this.playerElement.addEventListener('ended', () => {
                this.trigger(EVENT_ENDED);
            });
            this.playerElement.addEventListener('pause', () => {
                this.trigger(EVENT_PAUSE);
            });
            this.playerElement.addEventListener('play', () => {
                this.trigger(EVENT_PLAY);
            });
            this.trigger(EVENT_VOLUME_UPDATE, this.playerElement.volume * 100);
            if (screenFull) {
                document.addEventListener(screenFull[4], () => {
                    if (this.checkFull()) {
                        this.trigger(EVENT_ENTER_FULL_SCREEN);
                        return;
                    }
                    this.trigger(EVENT_EXIT_FULL_SCREEN);
                });
            }
        }

        private audioPlayer() {
            this.element.addClass('audio-player');
            this.element.html(`<div class="icon" title="播放">
            <i class="fa fa-play"></i>
        </div>
        <div class="slider">
            <div class="progress" title="0">
                <div class="progress-bar"></div>
            </div>
        </div>
        <div class="time">
            00:00/00:00
        </div>
        <div class="volume-icon">
            <i class="fa fa-volume-up"></i>
        </div>
        <div class="volume-slider">
            <div class="progress" title="100">
                <div class="progress-bar" style="width: 100%;"></div>
            </div>
        </div>`);
        }

        private videoMask() {
            this.element.addClass('video-player');
            this.element.html(`<div class="player-mask" title="此处有视频,点击即可播放">
            <i class="fa fa-play"></i>
        </div>`);
        }

        private videoFrame() {
            this.element.html(`
            <iframe class="player-frame" src="${this.options.src}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>`);
        }

        private videoPlayer() {
            this.element.html(`<video class="player-video" src="${this.options.src}"></video>
            <div class="player-bar">
                <div class="icon" title="播放">
                    <i class="fa fa-play"></i>
                </div>
                <div class="slider">
                    <div class="progress" title="0">
                        <div class="progress-bar"></div>
                    </div>
                </div>
                <div class="time">
                    00:00/00:00
                </div>
                <div class="volume-icon">
                    <i class="fa fa-volume-up"></i>
                </div>
                <div class="volume-slider">
                    <div class="progress" title="100">
                        <div class="progress-bar" style="width: 100%;"></div>
                    </div>
                </div>
                <div class="full-icon">
                    <i class="fa fa-expand"></i>
                </div>
            </div>`);
        }

        private formatMinute(time: number): string {
            return this.twoPad(Math.floor(time / 60)) + ':' + this.twoPad(Math.floor(time % 60));
        }

        private twoPad(n: number) {
            const str = n.toString();
            return str[1] ? str : '0' + str;
        }

        private fullScreen(element: any = document.documentElement) {
            if (!screenFull) {
                return;
            }
            element[screenFull[0]]();
        }

        private exitFullscreen(element: any = document) {
            if (!screenFull) {
                return;
            }
            element[screenFull[1]]();
        }

        private checkFull(): boolean {
            return screenFull && Boolean(document[screenFull[2]]);
        }
    }
    $.fn.player = function(option?: IPlayerOption) {
        return new MediaPlayer(this, option);
    };
})(jQuery);

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386

样式

.audio-player,
.video-player {
    .progress {
        height: .4em;
        border-radius: 0;
        margin-top: .2em;
        display: flex;
        overflow: hidden;
        line-height: 0;
        font-size: .75rem;
        background-color: #e9ecef;
        .progress-bar {
            cursor: default;
            display: flex;
            flex-direction: column;
            justify-content: center;
            overflow: hidden;
            color: #fff;
            text-align: center;
            white-space: nowrap;
            background-color: #007bff;
            transition: width .6s ease;
        }
        &:hover {
            height: .8em;
            margin-top: 0;
        }
    }
}
.audio-player {
    box-shadow: 0 2px 2px 0 rgb(0 0 0 / 7%), 0 1px 5px 0 rgb(0 0 0 / 10%);
    display: flex;
    box-sizing: border-box;
    line-height: 2.5em;
    i {
        font-style: normal;
    }
    .icon {
        width: 2.5em;
        text-align: center;
    }
    .slider {
        flex: 1;
        padding-top: 1em;
    }
    .time {
        line-height: 80rpx;
        font-size: .8em;
    }
    .volume-icon {
        padding-left: .5em;
    }
    .volume-slider {
        width: 3em;
        padding-top: 1em;
        padding-right: .5em;
    }
    &.player-mini {
        .volume-icon,
        .volume-slider {
            display: none;
        }
    }
}
.video-player {
    position: relative;
    box-shadow: 0 2px 2px 0 rgb(0 0 0 / 7%), 0 1px 5px 0 rgb(0 0 0 / 10%);
    i {
        font-style: normal;
    }
    .player-mask {
        background-color: #000;
        text-align: center;
        padding: 1em;
        .fa {
            color: #fff;
            font-size: 4em;
        }
        &:hover {
            .fa {
                color: #a10000;
            }
        }
    }
    .player-frame {
        border: 0;
        width: 100%;
        height: 100vw;
        max-height: 400px;
    }
    .player-video {
        border: 0;
        width: 100%;
        height: 100vw;
        max-height: 400px;
        background-color: #000;
        margin: 0;
    }
    .player-bar {
        display: flex;
        box-sizing: border-box;
        line-height: 2.5em;
        .icon {
            width: 2.5em;
            text-align: center;
        }
        .slider {
            flex: 1;
            padding-top: 1em;
        }
        .time {
            line-height: 80rpx;
            font-size: .8em;
        }
        .volume-icon {
            padding-left: .5em;
        }
        .volume-slider {
            width: 3em;
            padding-top: 1em;
            padding-right: .5em;
        }
        .full-icon {
            width: 2em;
            text-align: center;
        }
    }
    &.player-full {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        top: 0;
        z-index: 9999;
        background-color: #000;
        box-shadow: none;
        .player-video {
            width: 100%;
            height: 100%;
            max-height: 100%;
        }
        .player-bar {
            background-color: rgba(43,51,63,.7);
            color: #fff;
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            opacity: 0;
            transition: 1s opacity;
            &:hover {
                opacity: 1;
            }
        }
    }
}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156

使用


<link type="text/css" href="player.css" rel="stylesheet" media="all">


<div id="player"></div>


<script src="jquery.player.min.js"></script>

<script type="text/javascript">
jQuery(document).ready(function () {
    $('#player').player({
        src: "1616073275141535.mp3",
        type: "audio"
    });
});
</script>

123456789101112131415161718

请先引入jquery.js

参数说明

src  为资源文件的网络路径
type 可选值 video|audio|iframe ;默认为 video ; audio 即音频;iframe 为其他网址的视频,例如 bilibili 分享的链接

参考资料

  1. screenfull.js

转载请保留原文链接: https://zodream.cn/blog/id/198.html