Ueditor中支持音频

已有不少小伙伴给出了Ueditor中拓展支持插入音频功能的方法,但还是存在一些特例性的问题。踩完数个坑后总算把音频功能基本搞顺畅,这里整理汇总留个记录,以防年长健忘。

1. 修改ueditor.config.js文件,增加插入音频功能入口:

(1)在toolbars中增加'insertaudio'

toolbars: [[
    'source', '|', 'undo', 'redo', '|',
    ...
    'simpleupload',  'insertimage',  'emotion', 'insertaudio', 'insertvideo', 
    '|',
    'horizontal', 'date', 'time', 'spechars', '|', 'wordimage'
]]

(2)在labelMap中增加'insertaudio'对应的提示文字

labelMap: {
    'anchor' : '', 
    'vaecolor' : '自定义字体颜色',
    'insertaudio' : '音频',
}

2. 修改ueditor.all.js文件,增加插入音频页面和命令入口:

(1)在iframeUrlMap中增加插入音频页面的路径

var iframeUrlMap = {
    ...
    'insertaudio':'~/dialogs/audio/audio.html',
    'insertvideo':'~/dialogs/video/video.html',
    ...
    };

(2)在btnCmds中增加点击插入音频触发的命令

var btnCmds = ['undo', 'redo', 'formatmatch',
    ...
    'insertaudio'];

(3)在dialogBtns的ok属性中增加插入音频对话框

var dialogBtns = {
    noOk: ['searchreplace', 'help', 'spechars', 'webapp','preview'],
    ok: ['attachment', 'anchor', 'link', 'insertimage', 'map', 'gmap', 
        ...
        'insertaudio', 'vaecolor']
};

3. 增加audio.html:

(1)在ueditor/dialogs目录下增加audio目录,并增加audio.html,实现点击插入音频按钮时弹出的插入音频页面(此处可根据自身业务需求参照video.html或image.html来实现)

注:此页面中提供了插入音频和上传音频两种方式,现只实现了上传音频的功能。插入音频相对比较简单,不详述




    
    音频对话框
    
    
    
    
    
    
    
    
    


    
  px   px
px
px
0%

(2)增加audio.css用以实现audio.html中的相关样式(此处可照搬video.css或image.css后对应修改下)

/* 上传音频 */
.tabbody #upload.panel {
    width: 0;
    height: 0;
    overflow: hidden;
    position: absolute !important;
    clip: rect(1px, 1px, 1px, 1px);
    background: #fff;
    display: block;
}

.tabbody #upload.panel.focus {
    width: 100%;
    height: 300px;
    display: block;
    clip: auto;
}

#upload .titleBar {
    width: 100%;
    margin: 10px;
    font-size: 14px;
}

#upload .titleBar .uploadAudioTitle {
    margin-left: 5px;
    width: 88%;
}

// 后面省略,照搬后修改即可
...

(3)增加audio.js用以实现audio.html中的上传音频、插入音频代码等操作(同样的,照搬vedio.js或image.js后对应修改即可)

注:涉及到几个特例化的地方:①音频上传至服务器或云存储,改造过图片上传的应该驾轻就熟了;②因要求给插入的音频配上标题,此处上传为单个音频文件上传,故要禁用多选批量上传;③开始上传、暂停上传、取消上传的相应功能;④最终insertList的结构,要传递给ueditor.all.js中的insertaudio命令以向编辑器内插入音频控件代码

(function () {

    var insertaudio,
        uploadaudio;
    // 音频文件key前缀
    var keyPrefix = editor.getOpt('keyPrefix') + '/audio';
    
    window.onload = function () {
        initTabs();
        initButtons();
    };

    /* 初始化tab标签 */
    function initTabs() {
        var tabs = $G('tabhead').children;
        var audio = editor.selection.getRange().getClosedNode();
        var id = tabs[0].getAttribute('data-content-id');
        for (var i = 0; i < tabs.length; i++) {
            domUtils.on(tabs[i], "click", function (e) {
                var j, bodyId, target = e.target || e.srcElement;
                id = target.getAttribute('data-content-id');
                for (j = 0; j < tabs.length; j++) {
                    bodyId = tabs[j].getAttribute('data-content-id');
                    if(tabs[j] == target){
                        domUtils.addClass(tabs[j], 'focus');
                        domUtils.addClass($G(bodyId), 'focus');
                    }else {
                        domUtils.removeClasses(tabs[j], 'focus');
                        domUtils.removeClasses($G(bodyId), 'focus');
                    }
                }
            });
        }
        switch (id) {
        case 'remote':  // 插入音频/远程音频(预留)
            insertaudio = insertaudio || new RemoteAudio();
            break;
        case 'upload':  // 上传音频(主要)
            uploadaudio = uploadaudio || new UploadAudio('queueList');
            break;
        }
    }

    /* 初始化onok事件 */
    function initButtons() {
        dialog.onok = function () {
            var remote = false, list = [], id, tabs = $G('tabhead').children;
            for (var i = 0; i < tabs.length; i++) {
                if (domUtils.hasClass(tabs[i], 'focus')) {
                    id = tabs[i].getAttribute('data-content-id');
                    break;
                }
            }
            switch (id) {
                case 'remote':
                    list = insertaudio.getInsertList();
                    break;
                case 'upload':
                    list = uploadaudio.getInsertList();
                    var count = uploadaudio.getQueueCount();
                    if (count) {
                        $('.info', '#queueList').html('' + '还有2个未上传文件'.replace(/[\d]/, count) + '');
                        return false;
                    }
                    
                    // 配上标题
                    var title = $('.uploadAudioTitle').val();
                    if (!title || $.trim(title) == '') {
                        alert('请填写标题');
                        $('.uploadAudioTitle').focus();
                        return false;
                    }
                    if(list) {
                        for(var i = 0; i < list.length; i++) {
                            var f = list[i];
                            f['title'] = title;
                            list[i] = f;
                        }
                    }
                    break;
            }
            
            if(list) {
                editor.execCommand('insertaudio', list);
                remote && editor.fireEvent("catchRemoteAudio");
            }
        };
    }

    /* 上传音频 */
    function UploadAudio(target) {
        this.$wrap = target.constructor == String ? $('#' + target) : $(target);
        this.init();
    }
    UploadAudio.prototype = {
        init: function () {
            this.audioList = [];
            this.initContainer();
            this.initUploader();
        },
        initContainer: function () {
            this.$queue = this.$wrap.find('.filelist');
        },
        /* 初始化容器 */
        initUploader: function () {
            var _this = this,
                $ = jQuery,    // just in case. Make sure it's not an other libaray.
                $wrap = _this.$wrap,
            // 文件容器
                $queue = $wrap.find('.filelist'),
            // 状态栏,包括进度和控制按钮
                $statusBar = $wrap.find('.statusBar'),
            // 文件总体选择信息。
                $info = $statusBar.find('.info'),
            // 上传按钮
                $upload = $wrap.find('.uploadBtn'),
            // 上传按钮
                $filePickerBtn = $wrap.find('.filePickerBtn'),
            // 上传按钮
                $filePickerBlock = $wrap.find('.filePickerBlock'),
            // 没选择文件之前的内容。
                $placeHolder = $wrap.find('.placeholder'),
            // 总体进度条
                $progress = $statusBar.find('.progress').hide(),
            // 添加的文件数量
                fileCount = 0,
            // 添加的文件总大小
                fileSize = 0,
            // 优化retina, 在retina下这个值是2
                ratio = window.devicePixelRatio || 1,
            // 缩略图大小
                thumbnailWidth = 550 * ratio,
                thumbnailHeight = 113 * ratio,
            // 可能有pedding, ready, uploading, confirm, done.
                state = '',
            // 所有文件的进度信息,key为file id
                percentages = {},
                supportTransition = (function () {
                    var s = document.createElement('p').style,
                        r = 'transition' in s ||
                            'WebkitTransition' in s ||
                            'MozTransition' in s ||
                            'msTransition' in s ||
                            'OTransition' in s;
                    s = null;
                    return r;
                })(),
            // WebUploader实例
                uploader,
                actionUrl = editor.getActionUrl(editor.getOpt('audioActionName')),
                acceptExtensions = (editor.getOpt('audioAllowFiles') || []).join('').replace(/\./g, ',').replace(/^[,]/, ''),
                audioMaxSize = editor.getOpt('audioMaxSize'),
                imageCompressBorder = editor.getOpt('imageCompressBorder');

            if (!WebUploader.Uploader.support()) {
                $('#filePickerReady').after($('
').html(lang.errorNotSupport)).hide(); return; } else if (!editor.getOpt('audioActionName')) { $('#filePickerReady').after($('
').html(lang.errorLoadConfig)).hide(); return; } uploader = _this.uploader = WebUploader.create({ pick: { id: '#filePickerReady', label: lang.uploadSelectFile, multiple: false // 限制为单选 }, accept: { title: 'Audios', extensions: acceptExtensions, mimeTypes: 'audio/mp3,audio/amr,audio/wma,audio/wav' }, swf: '../../third-party/webuploader/Uploader.swf', server: actionUrl, fileVal: editor.getOpt('audioFieldName'), duplicate: false, fileNumLimit: 1, // 限制为单个文件 fileSingleSizeLimit: audioMaxSize // 默认 30 M }); uploader.addButton({ id: '#filePickerBlock' }); // uploader.addButton({ // id: '#filePickerBtn', // label: lang.uploadAddFile // }); setState('pedding'); // 当有文件添加进来时执行,负责view的创建 function addFile(file) { var $li = $('
  • ' + '

    ' + file.name + '

    ' + '

    ' + '
  • '), $btns = $('
    ' + '' + lang.uploadDelete + '' + '' + lang.uploadTurnRight + '' + '' + lang.uploadTurnLeft + '
    ').appendTo($li), $prgress = $li.find('p.progress span'), $wrap = $li.find('p.imgWrap'), $info = $('

    ').hide().appendTo($li), showError = function (code) { switch (code) { case 'exceed_size': text = lang.errorExceedSize; break; case 'interrupt': text = lang.errorInterrupt; break; case 'http': text = lang.errorHttp; break; case 'not_allow_type': text = lang.errorFileType; break; default: text = lang.errorUploadRetry; break; } $info.text(text).show(); }; if (file.getStatus() === 'invalid') { showError(file.statusText); } else { percentages[ file.id ] = [ file.size, 0 ]; file.rotation = 0; /* 检查文件格式 */ if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) { showError('not_allow_type'); uploader.removeFile(file); } } file.on('statuschange', function (cur, prev) { if (prev === 'progress') { $prgress.hide().width(0); } else if (prev === 'queued') { $li.off('mouseenter mouseleave'); $btns.remove(); } // 成功 if (cur === 'error' || cur === 'invalid') { showError(file.statusText); percentages[ file.id ][ 1 ] = 1; } else if (cur === 'interrupt') { showError('interrupt'); } else if (cur === 'queued') { percentages[ file.id ][ 1 ] = 0; } else if (cur === 'progress') { $info.hide(); $prgress.css('display', 'block'); } else if (cur === 'complete') { } $li.removeClass('state-' + prev).addClass('state-' + cur); }); $li.on('mouseenter', function () { $btns.stop().animate({height: 30}); }); $li.on('mouseleave', function () { $btns.stop().animate({height: 0}); }); $btns.on('click', 'span', function () { var index = $(this).index(), deg; switch (index) { case 0: uploader.removeFile(file); return; case 1: file.rotation += 90; break; case 2: file.rotation -= 90; break; } if (supportTransition) { deg = 'rotate(' + file.rotation + 'deg)'; $wrap.css({ '-webkit-transform': deg, '-mos-transform': deg, '-o-transform': deg, 'transform': deg }); } else { $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')'); } }); $li.insertBefore($filePickerBlock); // 隐藏继续添加控件,设置为单个文件上传 $filePickerBlock.hide(); } // 取消上传 function cancelFile(file) { var $li = $('#' + file.id); var spans = $progress.children(); spans.eq(0).text('0%'); spans.eq(1).css('width', '0%'); $progress.css('display', 'none'); $('.statusBar').children('.info').css('display', 'inline-block'); $('.error').remove(); var upBtn = $('.uploadBtn'); upBtn.removeClass('state-paused disabled'); upBtn.addClass('state-ready'); upBtn.html(lang.uploadStart); } // 负责view的销毁 function removeFile(file) { var $li = $('#' + file.id); delete percentages[ file.id ]; updateTotalProgress(); $li.off().find('.file-panel').off().end().remove(); // 显示继续添加控件 $filePickerBlock.show(); } function updateTotalProgress() { var loaded = 0, total = 0, spans = $progress.children(), percent; $.each(percentages, function (k, v) { total += v[ 0 ]; loaded += v[ 0 ] * v[ 1 ]; }); percent = total ? loaded / total : 0; spans.eq(0).text(Math.round(percent * 100) + '%'); spans.eq(1).css('width', Math.round(percent * 100) + '%'); updateStatus(); } function setState(val, files) { if (val != state) { var stats = uploader.getStats(); $upload.removeClass('state-' + state); $upload.addClass('state-' + val); switch (val) { /* 未选择文件 */ case 'pedding': $queue.addClass('element-invisible'); $statusBar.addClass('element-invisible'); $placeHolder.removeClass('element-invisible'); $progress.hide(); $info.hide(); uploader.refresh(); break; /* 可以开始上传 */ case 'ready': $placeHolder.addClass('element-invisible'); $queue.removeClass('element-invisible'); $statusBar.removeClass('element-invisible'); $progress.hide(); $info.show(); $upload.text(lang.uploadStart); uploader.refresh(); break; /* 上传中 */ case 'uploading': $progress.show(); $info.hide(); // $upload.text(lang.uploadPause); $upload.text(lang.uploadCancel); break; /* 暂停上传 */ case 'paused': $progress.show(); $info.hide(); $upload.text(lang.uploadContinue); break; /* 取消上传 */ case 'cancel': $placeHolder.addClass('element-invisible'); $queue.removeClass('element-invisible'); $statusBar.removeClass('element-invisible'); $progress.hide(); $info.show(); $upload.text(lang.uploadStart); uploader.refresh(); break; case 'confirm': $progress.show(); $info.hide(); $upload.text(lang.uploadStart); stats = uploader.getStats(); if (stats.successNum && !stats.uploadFailNum) { setState('finish'); return; } break; case 'finish': $progress.hide(); $info.show(); if (stats.uploadFailNum) { $upload.text(lang.uploadRetry); } else { $upload.text(lang.uploadStart); } break; } state = val; updateStatus(); } if (!_this.getQueueCount()) { $upload.addClass('disabled') } else { $upload.removeClass('disabled') } } function updateStatus() { var text = '', stats; if (state === 'ready') { text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize)); } else if (state === 'confirm') { stats = uploader.getStats(); if (stats.uploadFailNum) { text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum); } } else { stats = uploader.getStats(); text = lang.updateStatusFinish.replace('_', fileCount). replace('_KB', WebUploader.formatSize(fileSize)). replace('_', stats.successNum); if (stats.uploadFailNum) { text += lang.updateStatusError.replace('_', stats.uploadFailNum); } } $info.html(text); } uploader.on('fileQueued', function (file) { fileCount++; fileSize += file.size; if (fileCount === 1) { $placeHolder.addClass('element-invisible'); $statusBar.show(); } addFile(file); }); uploader.on('fileDequeued', function (file) { fileCount--; fileSize -= file.size; removeFile(file); updateTotalProgress(); }); uploader.on('filesQueued', function (file) { if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) { setState('ready'); } updateTotalProgress(); }); uploader.on('all', function (type, files) { switch (type) { case 'uploadFinished': setState('confirm', files); break; case 'startUpload': /* 添加额外的GET参数 */ var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || ''; //url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params); uploader.option('server', editor.getOpt('imageUrl')); setState('uploading', files); break; case 'stopUpload': setState('paused', files); break; } }); uploader.on('uploadBeforeSend', function (file, data, header) { //这里可以通过data对象添加POST参数 header['X_Requested_With'] = 'XMLHttpRequest'; // 上传token var token = getUploadToken4UE(); if (token == null) { alert('获取上传token异常,请稍后再试~'); return false; } data['token'] = token; // 文件key data['key'] = keyPrefix + '/' + uuid(); }); uploader.on('uploadProgress', function (file, percentage) { var $li = $('#' + file.id), $percent = $li.find('.progress span'); $percent.css('width', percentage * 100 + '%'); percentages[ file.id ][ 1 ] = percentage; updateTotalProgress(); }); uploader.on('uploadSuccess', function (file, ret) { var $file = $('#' + file.id); try { var responseText = (ret._raw || ret), json = utils.str2json(responseText); if (json.state == 'SUCCESS') { //_this.audioList.push(json); _this.audioList[$file.index()] = json; //按选择好的文件列表顺序存储 $file.append(''); } else { $file.find('.error').text(json.state).show(); } } catch (e) { $file.find('.error').text(lang.errorServerUpload).show(); } }); uploader.on('uploadError', function (file, code) { }); uploader.on('error', function (code, file) { if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') { addFile(file); } }); uploader.on('uploadComplete', function (file, ret) { }); $upload.on('click', function () { if ($(this).hasClass('disabled')) { return false; } if (state === 'ready') { uploader.upload(); } else if (state === 'paused') { uploader.upload(); } else if (state === 'cancel') { uploader.upload(); } else if (state === 'uploading') { // uploader.stop(); // 调整为取消上传 var file = uploader.getFiles()[0]; uploader.stop(file); // removeFile(file); cancelFile(file); // setState('cancel'); } }); $upload.addClass('state-' + state); updateTotalProgress(); }, getQueueCount: function () { var file, i, status, readyFile = 0, files = this.uploader.getFiles(); for (i = 0; file = files[i++]; ) { status = file.getStatus(); if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++; } return readyFile; }, destroy: function () { this.$wrap.remove(); }, getInsertList: function () { var i, data, list = [], prefix = editor.getOpt('audioUrlPrefix'); for (i = 0; i < this.audioList.length; i++) { data = this.audioList[i]; if(data == undefined){ continue; } //修改END list.push({ src: prefix + data.key, key: + new Date() // 以时间戳作为音频控件父div的id }); } return list; } }; })();

    (4)修改ueditor.css文件,增加插入音频按钮图标及页面窗口的相关样式:

    .edui-default  .edui-for-insertaudio .edui-icon {
        background-position: -320px -20px;
    }
    
    /*audio-dialog*/
    .edui-default .edui-for-insertaudio .edui-dialog-content {
        width: 590px;
        height: 390px;
    }
    
    /* audio*/
    .edui-default  .edui-for-insertaudio .edui-icon {
        background-image: url(../images/audio.png) !important;
    }
    

    音频按钮图标放在ueditor目录的themes/default/images下。

    (5)修改zh-cn.js文件,增加insertaudio的相关配置(类同insertvideo):

    'insertaudio' : {
            'static' : {
                'lang_tab_remote' : "插入音频",
                'lang_tab_upload' : "上传音频",
            },
            'uploadSelectFile' : '点击选择音频文件',
            'uploadAddFile' : '继续添加',
            'uploadStart' : '开始上传',
            'uploadPause' : '暂停上传',
            'uploadContinue' : '继续上传',
            'uploadCancel' : '取消上传',
            'uploadRetry' : '重试上传',
            'uploadDelete' : '删除',
            'uploadTurnLeft' : '向左旋转',
            'uploadTurnRight' : '向右旋转',
            'uploadPreview' : '预览中',
            'uploadNoPreview' : '不能预览',
            'updateStatusReady' : '选中_个音频文件,共_KB。',
            'updateStatusConfirm' : '已成功上传_个音频文件,_个音频文件上传失败',
            'updateStatusFinish' : '共_个(_KB),_个上传成功',
            'updateStatusError' : ',_个音频文件上传失败。',
            'errorNotSupport' : 'WebUploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器。',
            'errorLoadConfig' : '后端配置项没有正常加载,上传插件不能正常使用!',
            'errorExceedSize' : '文件大小超出',
            'errorFileType' : '文件格式不允许',
            'errorInterrupt' : '文件传输中断',
            'errorUploadRetry' : '上传失败,请重试',
            'errorHttp' : 'http请求错误',
            'errorServerUpload' : '服务器返回出错',
            'remoteLockError' : "宽高不正确,不能所定比例",
            'numError' : "请输入正确的长度或者宽度值!例如:123,400",
            'audioUrlError' : "不允许的音频格式或者图片域!",
            'audioLoadError' : "音频加载失败!请检查链接地址或网络状态!",
            'searchRemind' : "请输入搜索关键词",
            'searchLoading' : "音频加载中,请稍后……",
            'searchRetry' : " :( ,抱歉,没有找到音频!请重试一次!"
        },
    

    (6)修改config.json文件,增加音频上传的相关配置

    /* 上传音频配置项 */
    "audioActionName": "uploadaudio", /* 执行上传音频的action名称 */
    "audioFieldName": "file", /* 提交的音频表单名称 */
    "audioMaxSize": 30720000, /* 上传大小限制,单位B */
    "audioAllowFiles": [".mp3", ".wma", ".wav", ".amr"], /* 上传音频格式限制 */
    "audioUrlPrefix": "http://audio.ushallnotpass.com/" /* 音频访问路径前缀 */
    

    4. 修改ueditor.all.js文件,增加audio插件

    audio插件相关代码:

       /**
         * audio插件,为UEditor提供音频插入支持 
         */
        UE.plugins['audio'] = function (){
            var me = this;
            // 从publis.js中获取的静态文件路径,需自行修改设置
            var playicon = staticpath + 'audio/play.png';
            var pauseicon = staticpath + 'audio/pause.png';
            
            // 内容填入后初始化音频控件
            me.addListener("afterSetContent", function() {
                var audioArr = me.document.getElementsByTagName('audio');
                if(audioArr) {
                    $.each(audioArr, function(i, a) {
                        var aDiv = domUtils.findParent(a, function(node) {
                            return node.className === 'audio-wrapper';
                        });
                        if(aDiv) {
                            initAudioEvent(aDiv);
                        }
                    });
                }
            });
            
            /**
             * 插入音频
             * @command insertaudio
             * @method execCommand
             * @param { String } cmd 命令字符串
             * @param { Object } audioObjs 键值对对象, 描述一个音频的所有属性
             * @example
             * ```javascript
             *
             * var audioObjs = {
             *      // 音频地址
             *      src: 'http://www.xxx.com/yyy',
             *      // 音频标题
             *      title: 'this is a title'
             * };
             *
             * //editor 是编辑器实例
             * //向编辑器插入单个音频
             * editor.execCommand( 'insertaudio', audioObjs );
             * ```
             */
            UE.commands["insertaudio"] = {
                execCommand: function (cmd, audioObjs) {
                    audioObjs = utils.isArray(audioObjs) ? audioObjs : [audioObjs];
                    if (!audioObjs) {
                        return false;
                    }
                    var html = [];
                    for (var i = 0; i < audioObjs.length; i++) {
                        var src = createAudioHtml(audioObjs[i].key, audioObjs[i].src, audioObjs[i].title);
                        html.push(src);
                    }
                    me.execCommand("inserthtml", html.join(""));
                    // 初始化音频控件
                    initAudio(audioObjs);
                    me.focus();
                }
            };
            
            /**
             * 构造音频控件html
             * 
             * @param {string} audioDivId - 音频控件父div的id
             * @param {string} audioSrc - 音频控件地址
             * @param {string} audioTitle - 音频标题
             */
            function createAudioHtml(audioDivId, audioSrc, audioTitle) {
                var src = '
    ' +'' +'
    ' +'' +'' +'
    ' +'
    ' +'

    ' + audioTitle + '

    ' +'
    ' +'' +'
    ' +'
    ' +'
    ' +'00:0000:00' +'
    ' +'
    ' +'

    '; return src; } /** * 初始化音频控件 * * @param {array} audioObjs - 音频父div数组 */ function initAudio(audioObjs) { if(audioObjs) { for(var i = 0; i < audioObjs.length; i++) { var audioDiv = me.document.getElementById(audioObjs[i].key); initAudioEvent(audioDiv); } } } /** * 初始化音频控制事件 * * @param {object} audioDiv - 音频父div */ function initAudioEvent(audioDiv) { // div子节点 var divArr = domUtils.getElementsByTagName(audioDiv, 'div'); // audio控件 var audio = domUtils.getElementsByTagName(audioDiv, 'audio')[0]; // 控制音频文件名显示宽度 var audioRight = domUtils.filterNodeList(divArr, function(node) { return node.className === 'audio-right'; }); var title = domUtils.getElementsByTagName(audioRight, 'p')[0]; domUtils.setStyle(title, 'max-width', domUtils.getComputedStyle(audioRight, 'width')); // 右侧div组 var rightDivArr = domUtils.getElementsByTagName(audioRight, 'div'); // 进度条div var progressDiv = domUtils.filterNodeList(rightDivArr, function(node) { return node.className === 'progress-bar-bg'; }); // 已播放进度条 var progressBar = domUtils.getElementsByTagName(progressDiv, 'div')[0]; domUtils.setStyle(progressBar, 'width', 0); // 初始化为未播放状态 // 进度条上控制点 var progressDot = domUtils.getElementsByTagName(progressDiv, 'span')[0]; domUtils.setStyle(progressDot, 'left', 0); // 初始化为未播放状态 // 时间div var timeDiv = domUtils.filterNodeList(rightDivArr, function(node) { return node.className === 'audio-time'; }); // 时间span组 var timeSpanArr = domUtils.getElementsByTagName(timeDiv, 'span'); // 已播放时间 var audioCurTime = domUtils.filterNodeList(timeSpanArr, function(node) { return node.className === 'audioCurTime'; }); audioCurTime.innerHTML = '00:00'; // 初始化为0 // 总时间 var audioTotalTime = domUtils.filterNodeList(timeSpanArr, function(node) { return node.className === 'audioTotalTime'; }); // 点击播放/暂停图片时,控制音乐的播放与暂停 var playerDiv = domUtils.filterNodeList(divArr, function(node) { return node.className === 'audio-left'; }); // 播放图标、暂停图标 var playerImgs = domUtils.getElementsByTagName(playerDiv, 'img'); var playImg = domUtils.filterNodeList(playerImgs, function(node) { return node.className === 'playicon'; }); var pauseImg = domUtils.filterNodeList(playerImgs, function(node) { return node.className === 'pauseicon'; }); // 初始化播放图标 domUtils.setStyle(playImg, 'display', 'initial'); domUtils.setStyle(pauseImg, 'display', 'none'); // 音频准备就绪后执行 audio.addEventListener('canplay', function() { audioTotalTime.innerHTML = transTime(audio.duration); // 初始化为音频总时长 }); // 播放事件 domUtils.on(playImg, 'click', function(e) { // 禁止事件冒泡 if (e && e.stopPropagation) { e.stopPropagation(); } else { window.event.cancelBubble = true; } // 监听音频播放时间并更新进度条 audio.addEventListener('timeupdate', function () { updateProgress(audio, progressBar, progressDot, audioCurTime); }, false); // 监听播放完成事件 audio.addEventListener('ended', function () { audioEnded(progressBar, progressDot, audioCurTime, playImg, pauseImg) }, false); // 播放 audio.play(); // 切换播放暂停图标 domUtils.setStyle(playImg, 'display', 'none'); domUtils.setStyle(pauseImg, 'display', 'initial'); // 暂停其他正在播放的音频 var audios = me.document.getElementsByTagName('audio'); for (var i = 0; i < audios.length; i++) { var parentDiv = domUtils.findParent(audios[i], function(node) { return node.className === 'audio-wrapper'; }); if (parentDiv.id != audioDiv.id && !audios[i].paused) { audios[i].pause(); var playerDiv = domUtils.getNextDomNode(audios[i]); var players = domUtils.getElementsByTagName(playerDiv, 'img'); var play = domUtils.filterNodeList(players, function(node) { return node.className === 'playicon'; }); var pause = domUtils.filterNodeList(players, function(node) { return node.className === 'pauseicon'; }); domUtils.setStyle(play, 'display', 'initial'); domUtils.setStyle(pause, 'display', 'none'); } } }); // 暂停事件 domUtils.on(pauseImg, 'click', function(e) { // 禁止事件冒泡 if (e && e.stopPropagation) { e.stopPropagation(); } else { window.event.cancelBubble = true; } // 暂停 audio.pause(); // 切换播放暂停图标 domUtils.setStyle(playImg, 'display', 'initial'); domUtils.setStyle(pauseImg, 'display', 'none'); }); // 点击进度条跳到指定点播放 domUtils.on(progressDiv, 'mousedown', function(e) { // 只有音乐开始播放后才可以调节,已经播放过但暂停了的也可以 if (!audio.paused || audio.currentTime != 0) { var pgsWidth = parseInt(domUtils.getComputedStyle(progressDiv, 'width')); var rate = e.offsetX / pgsWidth; audio.currentTime = audio.duration * rate; updateProgress(audio, progressBar, progressDot, audioCurTime); } }); // 鼠标拖动进度点时可以调节进度 // 只有音乐开始播放后才可以调节,已经播放过但暂停了的也可以 // 鼠标按下时 domUtils.on(progressDot, 'mousedown', function(e) { if (!audio.paused || audio.currentTime != 0) { var oriLeft = progressDot.offsetLeft; var mouseX = e.clientX; var maxLeft = oriLeft; // 向左最大可拖动距离 var maxRight = progressDiv.offsetWidth - oriLeft; // 向右最大可拖动距离 // 禁止默认的选中事件(避免鼠标拖拽进度点的时候选中文字) if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } // 禁止事件冒泡 if (e && e.stopPropagation) { e.stopPropagation(); } else { window.event.cancelBubble = true; } // 开始拖动 me.document.onmousemove = function (e) { var length = e.clientX - mouseX; if (length > maxRight) { length = maxRight; } else if (length < -maxLeft) { length = -maxLeft; } var pgsWidth = parseInt(domUtils.getComputedStyle(progressDiv, 'width')); var rate = (oriLeft + length) / pgsWidth; audio.currentTime = audio.duration * rate; updateProgress(audio, progressBar, progressDot, audioCurTime); }; // 拖动结束 me.document.onmouseup = function () { me.document.onmousemove = null; me.document.onmouseup = null; }; } }); } /** * 更新进度条与当前播放时间 * * @param {object} audio - audio对象 * @param {object} progressBar - 进度条对象 * @param {object} progressDot - 进度条控制点对象 * @param {object} audioCurTime - 当前播放时间对象 */ function updateProgress(audio, progressBar, progressDot, audioCurTime) { var value = audio.currentTime / audio.duration; domUtils.setStyle(progressBar, 'width', value * 100 + '%'); domUtils.setStyle(progressDot, 'left', value * 100 + '%'); audioCurTime.innerHTML = transTime(audio.currentTime); } /** * 播放完成时把进度调回开始的位置 * * @param {object} progressBar - 进度条对象 * @param {object} progressDot - 进度条控制点对象 * @param {object} audioCurTime - 当前播放时间对象 * @param {object} playImg- 播放按钮图标 * @param {object} pauseImg- 暂停按钮图标 */ function audioEnded(progressBar, progressDot, audioCurTime, playImg, pauseImg) { domUtils.setStyle(progressBar, 'width', 0); domUtils.setStyle(progressDot, 'left', 0); domUtils.setStyle(playImg, 'display', 'initial'); domUtils.setStyle(pauseImg, 'display', 'none'); audioCurTime.innerHTML = '00:00'; } /** * 音频播放时间换算 * * @param {number} value - 音频当前播放时间,单位秒 */ function transTime(value) { var time = ""; var h = parseInt(value / 3600); value %= 3600; var m = parseInt(value / 60); var s = parseInt(value % 60); if (h > 0) { time = formatTime(h + ":" + m + ":" + s); } else { time = formatTime(m + ":" + s); } return time; } /** * 格式化时间显示,补零对齐 * * eg:2:4 --> 02:04 * @param {string} value - 形如 h:m:s 的字符串 */ function formatTime(value) { var time = ""; var s = value.split(':'); var i = 0; for (; i < s.length - 1; i++) { time += s[i].length == 1 ? ("0" + s[i]) : s[i]; time += ":"; } time += s[i].length == 1 ? ("0" + s[i]) : s[i]; return time; } };

    5. 解决ueditor复制粘贴冲突

    上述几个步骤完成后,插入音频功能就已实现了。但在编辑器中插入音频后再粘贴其他内容时,会发现音频控件的进度条样式出了问题。这是因为ueditor在粘贴后会对空内容div进行删除,而audio插件所插入的音频控件中有空内容div。可修改相应方法将音频控件中的div排除。

    在ueditor.all.js中找到filter(div)方法,再找到以下这段代码:

    if (browser.webkit) {
        var br = root.lastChild();
        if (br && br.type == 'element' && br.tagName == 'br') {
            root.removeChild(br)
        }
        utils.each(me.body.querySelectorAll('div'), function (node) {
            if (domUtils.isEmptyBlock(node)) {
                domUtils.remove(node,true)
            }
        })
     }
    

    对删除div的判定条件进行修改,排除音频控件的相关div:

    if (domUtils.isEmptyBlock(node) && node.className != 'progress-bar-bg' && node.className != 'progressBar') {
        domUtils.remove(node,true)
     }
    

    6. 文章页面音频控件初始化

    在ueditor中编辑完文章内容之后,在展示文章时也需要对文章内容中的音频控件进行初始化,故需要提供相应的初始化方法供页面调用。实现方式比较简单,和audio插件中的方法基本一致,不详述。

    另若需支持移动端的访问,则需要对点击、滑动等事件进行区分处理,对PC端访问要响应mouse事件,对移动端访问要响应touch事件:

    // 定义不同端事件调用
    var hasTouch = 'ontouchstart' in window,
    startEvent = hasTouch ? 'touchstart' : 'mousedown',
    moveEvent = hasTouch ? 'touchmove' : 'mousemove',
    endEvent = hasTouch ? 'touchend' : 'mouseup',
    cancelEvent = hasTouch ? 'touchcancel' : 'mouseup';
    

    7. 效果图

    (1)Ueditor工具栏上音频图标

    Ueditor中支持音频_第1张图片
    1.png

    (2)上传音频

    Ueditor中支持音频_第2张图片
    2.png

    (3)Ueditor内传入音频后效果

    Ueditor中支持音频_第3张图片
    3.png

    (4)移动端文章内音频效果

    Ueditor中支持音频_第4张图片
    4.png

    你可能感兴趣的:(Ueditor中支持音频)