已有不少小伙伴給出了Ueditor中拓展支持插入音頻功能的方法橱健,但還是存在一些特例性的問(wèn)題斯撮。踩完數(shù)個(gè)坑后總算把音頻功能基本搞順暢,這里整理匯總留個(gè)記錄础锐,以防年長(zhǎng)健忘嗓节。
1. 修改ueditor.config.js文件,增加插入音頻功能入口:
(1)在toolbars中增加'insertaudio'
toolbars: [[
'source', '|', 'undo', 'redo', '|',
...
'simpleupload', 'insertimage', 'emotion', 'insertaudio', 'insertvideo',
'|',
'horizontal', 'date', 'time', 'spechars', '|', 'wordimage'
]]
(2)在labelMap中增加'insertaudio'對(duì)應(yīng)的提示文字
labelMap: {
'anchor' : '',
'vaecolor' : '自定義字體顏色',
'insertaudio' : '音頻',
}
2. 修改ueditor.all.js文件郁稍,增加插入音頻頁(yè)面和命令入口:
(1)在iframeUrlMap中增加插入音頻頁(yè)面的路徑
var iframeUrlMap = {
...
'insertaudio':'~/dialogs/audio/audio.html',
'insertvideo':'~/dialogs/video/video.html',
...
};
(2)在btnCmds中增加點(diǎn)擊插入音頻觸發(fā)的命令
var btnCmds = ['undo', 'redo', 'formatmatch',
...
'insertaudio'];
(3)在dialogBtns的ok屬性中增加插入音頻對(duì)話框
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,實(shí)現(xiàn)點(diǎn)擊插入音頻按鈕時(shí)彈出的插入音頻頁(yè)面(此處可根據(jù)自身業(yè)務(wù)需求參照video.html或image.html來(lái)實(shí)現(xiàn))
注:此頁(yè)面中提供了插入音頻和上傳音頻兩種方式耀怜,現(xiàn)只實(shí)現(xiàn)了上傳音頻的功能恢着。插入音頻相對(duì)比較簡(jiǎn)單,不詳述
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>音頻對(duì)話框</title>
<script type="text/javascript" src="../internal.js"></script>
<!-- jquery -->
<script type="text/javascript" src="../../third-party/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="../../../mains/public/public.js"></script>
<!-- webuploader -->
<script src="../../third-party/webuploader/webuploader.min.js"></script>
<link rel="stylesheet" type="text/css" href="../../third-party/webuploader/webuploader.css">
<!-- image dialog -->
<link rel="stylesheet" href="audio.css" type="text/css" />
</head>
<body>
<div class="wrapper">
<div id="tabhead" class="tabhead">
<span class="tab focus" data-content-id="upload"><var id="lang_tab_upload"></var></span>
</div>
<div id="tabbody" class="tabbody">
<!-- 插入音頻 -->
<div id="remote" class="panel">
<div class="top">
<div class="row">
<label for="url"><var id="lang_input_url"></var></label>
<span><input class="text" id="url" type="text"/></span>
</div>
</div>
<div class="left">
<div class="row">
<label><var id="lang_input_size"></var></label>
<span><var id="lang_input_width"> </var><input class="text" type="text" id="width"/>px </span>
<span><var id="lang_input_height"> </var><input class="text" type="text" id="height"/>px </span>
<span><input id="lock" type="checkbox" disabled="disabled"><span id="lockicon"></span></span>
</div>
<div class="row">
<label><var id="lang_input_border"></var></label>
<span><input class="text" type="text" id="border"/>px </span>
</div>
<div class="row">
<label><var id="lang_input_vhspace"></var></label>
<span><input class="text" type="text" id="vhSpace"/>px </span>
</div>
<div class="row">
<label><var id="lang_input_title"></var></label>
<span><input class="text" type="text" id="title"/></span>
</div>
</div>
<div class="right"><div id="preview"></div></div>
</div>
<!-- 上傳音頻 -->
<div id="upload" class="panel focus">
<div class="titleBar">
<label>標(biāo)題</label>
<input type="text" class="uploadAudioTitle"/>
</div>
<div id="queueList" class="queueList">
<div class="statusBar element-invisible">
<div class="progress">
<span class="text">0%</span>
<span class="percentage"></span>
</div><div class="info"></div>
<div class="btns">
<div id="filePickerBtn"></div>
<div class="uploadBtn"><var id="lang_start_upload"></var></div>
</div>
</div>
<div id="dndArea" class="placeholder">
<div class="filePickerContainer">
<div id="filePickerReady"></div>
</div>
</div>
<ul class="filelist element-invisible">
<li id="filePickerBlock" class="filePickerBlock"></li>
</ul>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="audio.js"></script>
</body>
</html>
(2)增加audio.css用以實(shí)現(xiàn)audio.html中的相關(guān)樣式(此處可照搬video.css或image.css后對(duì)應(yīng)修改下)
/* 上傳音頻 */
.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用以實(shí)現(xiàn)audio.html中的上傳音頻掰派、插入音頻代碼等操作(同樣的,照搬vedio.js或image.js后對(duì)應(yīng)修改即可)
注:涉及到幾個(gè)特例化的地方:①音頻上傳至服務(wù)器或云存儲(chǔ)左痢,改造過(guò)圖片上傳的應(yīng)該駕輕就熟了靡羡;②因要求給插入的音頻配上標(biāo)題系洛,此處上傳為單個(gè)音頻文件上傳,故要禁用多選批量上傳略步;③開(kāi)始上傳描扯、暫停上傳、取消上傳的相應(yīng)功能趟薄;④最終insertList的結(jié)構(gòu)绽诚,要傳遞給ueditor.all.js中的insertaudio命令以向編輯器內(nèi)插入音頻控件代碼
(function () {
var insertaudio,
uploadaudio;
// 音頻文件key前綴
var keyPrefix = editor.getOpt('keyPrefix') + '/audio';
window.onload = function () {
initTabs();
initButtons();
};
/* 初始化tab標(biāo)簽 */
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': // 插入音頻/遠(yuǎn)程音頻(預(yù)留)
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('<span style="color:red;">' + '還有2個(gè)未上傳文件'.replace(/[\d]/, count) + '</span>');
return false;
}
// 配上標(biāo)題
var title = $('.uploadAudioTitle').val();
if (!title || $.trim(title) == '') {
alert('請(qǐng)?zhí)顚憳?biāo)題');
$('.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'),
// 狀態(tài)欄,包括進(jìn)度和控制按鈕
$statusBar = $wrap.find('.statusBar'),
// 文件總體選擇信息杭煎。
$info = $statusBar.find('.info'),
// 上傳按鈕
$upload = $wrap.find('.uploadBtn'),
// 上傳按鈕
$filePickerBtn = $wrap.find('.filePickerBtn'),
// 上傳按鈕
$filePickerBlock = $wrap.find('.filePickerBlock'),
// 沒(méi)選擇文件之前的內(nèi)容恩够。
$placeHolder = $wrap.find('.placeholder'),
// 總體進(jìn)度條
$progress = $statusBar.find('.progress').hide(),
// 添加的文件數(shù)量
fileCount = 0,
// 添加的文件總大小
fileSize = 0,
// 優(yōu)化retina, 在retina下這個(gè)值是2
ratio = window.devicePixelRatio || 1,
// 縮略圖大小
thumbnailWidth = 550 * ratio,
thumbnailHeight = 113 * ratio,
// 可能有pedding, ready, uploading, confirm, done.
state = '',
// 所有文件的進(jìn)度信息,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實(shí)例
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($('<div>').html(lang.errorNotSupport)).hide();
return;
} else if (!editor.getOpt('audioActionName')) {
$('#filePickerReady').after($('<div>').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, // 限制為單個(gè)文件
fileSingleSizeLimit: audioMaxSize // 默認(rèn) 30 M
});
uploader.addButton({
id: '#filePickerBlock'
});
// uploader.addButton({
// id: '#filePickerBtn',
// label: lang.uploadAddFile
// });
setState('pedding');
// 當(dāng)有文件添加進(jìn)來(lái)時(shí)執(zhí)行羡铲,負(fù)責(zé)view的創(chuàng)建
function addFile(file) {
var $li = $('<li id="' + file.id + '">' +
'<p class="title">' + file.name + '</p>' +
'<p class="progress"><span></span></p>' +
'</li>'),
$btns = $('<div class="file-panel">' +
'<span class="cancel">' + lang.uploadDelete + '</span>' +
'<span class="rotateRight">' + lang.uploadTurnRight + '</span>' +
'<span class="rotateLeft">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),
$prgress = $li.find('p.progress span'),
$wrap = $li.find('p.imgWrap'),
$info = $('<p class="error"></p>').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);
// 隱藏繼續(xù)添加控件蜂桶,設(shè)置為單個(gè)文件上傳
$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);
}
// 負(fù)責(zé)view的銷毀
function removeFile(file) {
var $li = $('#' + file.id);
delete percentages[ file.id ];
updateTotalProgress();
$li.off().find('.file-panel').off().end().remove();
// 顯示繼續(xù)添加控件
$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;
/* 可以開(kāi)始上傳 */
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參數(shù) */
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) {
//這里可以通過(guò)data對(duì)象添加POST參數(shù)
header['X_Requested_With'] = 'XMLHttpRequest';
// 上傳token
var token = getUploadToken4UE();
if (token == null) {
alert('獲取上傳token異常,請(qǐng)稍后再試~');
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; //按選擇好的文件列表順序存儲(chǔ)
$file.append('<span class="success"></span>');
} 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();
// 調(diào)整為取消上傳
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() // 以時(shí)間戳作為音頻控件父div的id
});
}
return list;
}
};
})();
(4)修改ueditor.css文件也切,增加插入音頻按鈕圖標(biāo)及頁(yè)面窗口的相關(guān)樣式:
.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;
}
音頻按鈕圖標(biāo)放在ueditor目錄的themes/default/images下扑媚。
(5)修改zh-cn.js文件,增加insertaudio的相關(guān)配置(類同insertvideo):
'insertaudio' : {
'static' : {
'lang_tab_remote' : "插入音頻",
'lang_tab_upload' : "上傳音頻",
},
'uploadSelectFile' : '點(diǎn)擊選擇音頻文件',
'uploadAddFile' : '繼續(xù)添加',
'uploadStart' : '開(kāi)始上傳',
'uploadPause' : '暫停上傳',
'uploadContinue' : '繼續(xù)上傳',
'uploadCancel' : '取消上傳',
'uploadRetry' : '重試上傳',
'uploadDelete' : '刪除',
'uploadTurnLeft' : '向左旋轉(zhuǎn)',
'uploadTurnRight' : '向右旋轉(zhuǎn)',
'uploadPreview' : '預(yù)覽中',
'uploadNoPreview' : '不能預(yù)覽',
'updateStatusReady' : '選中_個(gè)音頻文件贾费,共_KB钦购。',
'updateStatusConfirm' : '已成功上傳_個(gè)音頻文件檐盟,_個(gè)音頻文件上傳失敗',
'updateStatusFinish' : '共_個(gè)(_KB)褂萧,_個(gè)上傳成功',
'updateStatusError' : ',_個(gè)音頻文件上傳失敗葵萎。',
'errorNotSupport' : 'WebUploader 不支持您的瀏覽器导犹!如果你使用的是IE瀏覽器,請(qǐng)嘗試升級(jí) flash 播放器羡忘。',
'errorLoadConfig' : '后端配置項(xiàng)沒(méi)有正常加載谎痢,上傳插件不能正常使用!',
'errorExceedSize' : '文件大小超出',
'errorFileType' : '文件格式不允許',
'errorInterrupt' : '文件傳輸中斷',
'errorUploadRetry' : '上傳失敗卷雕,請(qǐng)重試',
'errorHttp' : 'http請(qǐng)求錯(cuò)誤',
'errorServerUpload' : '服務(wù)器返回出錯(cuò)',
'remoteLockError' : "寬高不正確,不能所定比例",
'numError' : "請(qǐng)輸入正確的長(zhǎng)度或者寬度值节猿!例如:123,400",
'audioUrlError' : "不允許的音頻格式或者圖片域漫雕!",
'audioLoadError' : "音頻加載失敱踔觥!請(qǐng)檢查鏈接地址或網(wǎng)絡(luò)狀態(tài)浸间!",
'searchRemind' : "請(qǐng)輸入搜索關(guān)鍵詞",
'searchLoading' : "音頻加載中太雨,請(qǐng)稍后……",
'searchRetry' : " :( ,抱歉魁蒜,沒(méi)有找到音頻囊扳!請(qǐng)重試一次吩翻!"
},
(6)修改config.json文件,增加音頻上傳的相關(guān)配置
/* 上傳音頻配置項(xiàng) */
"audioActionName": "uploadaudio", /* 執(zhí)行上傳音頻的action名稱 */
"audioFieldName": "file", /* 提交的音頻表單名稱 */
"audioMaxSize": 30720000, /* 上傳大小限制锥咸,單位B */
"audioAllowFiles": [".mp3", ".wma", ".wav", ".amr"], /* 上傳音頻格式限制 */
"audioUrlPrefix": "http://audio.ushallnotpass.com/" /* 音頻訪問(wèn)路徑前綴 */
4. 修改ueditor.all.js文件狭瞎,增加audio插件
<audio>標(biāo)簽自帶樣式不太好看,此處audio插件對(duì)插入的音頻控件樣式進(jìn)行了改造搏予,并實(shí)現(xiàn)了相關(guān)的播放控制事件脚作。感謝Dandelion_drq
分享的解決方案:H5 <audio> 音頻標(biāo)簽自定義樣式修改以及添加播放控制事件
audio插件相關(guān)代碼:
/**
* audio插件,為UEditor提供音頻插入支持
*/
UE.plugins['audio'] = function (){
var me = this;
// 從publis.js中獲取的靜態(tài)文件路徑缔刹,需自行修改設(shè)置
var playicon = staticpath + 'audio/play.png';
var pauseicon = staticpath + 'audio/pause.png';
// 內(nèi)容填入后初始化音頻控件
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 鍵值對(duì)對(duì)象球涛, 描述一個(gè)音頻的所有屬性
* @example
* ```javascript
*
* var audioObjs = {
* // 音頻地址
* src: 'http://www.xxx.com/yyy',
* // 音頻標(biāo)題
* title: 'this is a title'
* };
*
* //editor 是編輯器實(shí)例
* //向編輯器插入單個(gè)音頻
* 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();
}
};
/**
* 構(gòu)造音頻控件html
*
* @param {string} audioDivId - 音頻控件父div的id
* @param {string} audioSrc - 音頻控件地址
* @param {string} audioTitle - 音頻標(biāo)題
*/
function createAudioHtml(audioDivId, audioSrc, audioTitle) {
var src = '<div class="audio-wrapper" id="' + audioDivId + '" style="background-color: #fcfcfc;margin: 10px auto;max-width: 670px;height: 90px;border: 1px solid #e0e0e0;">'
+'<audio><source src="' + audioSrc + '"></audio>'
+'<div class="audio-left" style="float: left;text-align: center;width: 22%;height: 100%;">'
+'<img src="' + playicon + '" class="playicon" style="width: 40px;position: relative;top: 25px;margin: 0;display: initial;cursor: pointer;"/>'
+'<img src="' + pauseicon + '" class="pauseicon" style="width: 40px;position: relative;top: 25px;margin: 0;display: none;cursor: pointer;"/>'
+'</div>'
+'<div class="audio-right" style="margin-right: 5%;float: right;width: 73%;height: 100%;">'
+'<p class="audio-title" style="max-width: 536px;font-size: 15px;height: 35%;margin: 8px 0;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">' + audioTitle + '</p>'
+'<div class="progress-bar-bg" style="background-color: #d9d9d9;position: relative;height: 2px;cursor: pointer;">'
+'<span class="progressDot" style="content: \' \';width: 12px;height: 12px;border-radius: 50%;-moz-border-radius: 50%;-webkit-border-radius: 50%;background-color: #3e87e8;position: absolute;left: 0;top: -5px;margin-left: 0px;cursor: pointer;"></span>'
+'<div class="progressBar" style="background-color: #649fec;width: 0;height: 2px;"></div>'
+'</div>'
+'<div class="audio-time" style="overflow: hidden;margin-top: -1px;">'
+'<span class="audioCurTime" style="float: left;margin-top:10px;font-size: 12px;color: #969696">00:00</span><span class="audioTotalTime" style="float: right;margin-top:10px;font-size: 12px;color: #969696">00:00</span>'
+'</div>'
+'</div>'
+'</div><br/>';
return src;
}
/**
* 初始化音頻控件
*
* @param {array} audioObjs - 音頻父div數(shù)組
*/
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子節(jié)點(diǎn)
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'));
// 右側(cè)div組
var rightDivArr = domUtils.getElementsByTagName(audioRight, 'div');
// 進(jìn)度條div
var progressDiv = domUtils.filterNodeList(rightDivArr, function(node) {
return node.className === 'progress-bar-bg';
});
// 已播放進(jìn)度條
var progressBar = domUtils.getElementsByTagName(progressDiv, 'div')[0];
domUtils.setStyle(progressBar, 'width', 0); // 初始化為未播放狀態(tài)
// 進(jìn)度條上控制點(diǎn)
var progressDot = domUtils.getElementsByTagName(progressDiv, 'span')[0];
domUtils.setStyle(progressDot, 'left', 0); // 初始化為未播放狀態(tài)
// 時(shí)間div
var timeDiv = domUtils.filterNodeList(rightDivArr, function(node) {
return node.className === 'audio-time';
});
// 時(shí)間span組
var timeSpanArr = domUtils.getElementsByTagName(timeDiv, 'span');
// 已播放時(shí)間
var audioCurTime = domUtils.filterNodeList(timeSpanArr, function(node) {
return node.className === 'audioCurTime';
});
audioCurTime.innerHTML = '00:00'; // 初始化為0
// 總時(shí)間
var audioTotalTime = domUtils.filterNodeList(timeSpanArr, function(node) {
return node.className === 'audioTotalTime';
});
// 點(diǎn)擊播放/暫停圖片時(shí),控制音樂(lè)的播放與暫停
var playerDiv = domUtils.filterNodeList(divArr, function(node) {
return node.className === 'audio-left';
});
// 播放圖標(biāo)校镐、暫停圖標(biāo)
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';
});
// 初始化播放圖標(biāo)
domUtils.setStyle(playImg, 'display', 'initial');
domUtils.setStyle(pauseImg, 'display', 'none');
// 音頻準(zhǔn)備就緒后執(zhí)行
audio.addEventListener('canplay', function() {
audioTotalTime.innerHTML = transTime(audio.duration); // 初始化為音頻總時(shí)長(zhǎng)
});
// 播放事件
domUtils.on(playImg, 'click', function(e) {
// 禁止事件冒泡
if (e && e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
// 監(jiān)聽(tīng)音頻播放時(shí)間并更新進(jìn)度條
audio.addEventListener('timeupdate', function () {
updateProgress(audio, progressBar, progressDot, audioCurTime);
}, false);
// 監(jiān)聽(tīng)播放完成事件
audio.addEventListener('ended', function () {
audioEnded(progressBar, progressDot,
audioCurTime, playImg, pauseImg)
}, false);
// 播放
audio.play();
// 切換播放暫停圖標(biāo)
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();
// 切換播放暫停圖標(biāo)
domUtils.setStyle(playImg, 'display', 'initial');
domUtils.setStyle(pauseImg, 'display', 'none');
});
// 點(diǎn)擊進(jìn)度條跳到指定點(diǎn)播放
domUtils.on(progressDiv, 'mousedown', function(e) {
// 只有音樂(lè)開(kāi)始播放后才可以調(diào)節(jié)亿扁,已經(jīng)播放過(guò)但暫停了的也可以
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);
}
});
// 鼠標(biāo)拖動(dòng)進(jìn)度點(diǎn)時(shí)可以調(diào)節(jié)進(jìn)度
// 只有音樂(lè)開(kāi)始播放后才可以調(diào)節(jié),已經(jīng)播放過(guò)但暫停了的也可以
// 鼠標(biāo)按下時(shí)
domUtils.on(progressDot, 'mousedown', function(e) {
if (!audio.paused || audio.currentTime != 0) {
var oriLeft = progressDot.offsetLeft;
var mouseX = e.clientX;
var maxLeft = oriLeft; // 向左最大可拖動(dòng)距離
var maxRight = progressDiv.offsetWidth - oriLeft; // 向右最大可拖動(dòng)距離
// 禁止默認(rèn)的選中事件(避免鼠標(biāo)拖拽進(jìn)度點(diǎn)的時(shí)候選中文字)
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
// 禁止事件冒泡
if (e && e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
// 開(kāi)始拖動(dòng)
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);
};
// 拖動(dòng)結(jié)束
me.document.onmouseup = function () {
me.document.onmousemove = null;
me.document.onmouseup = null;
};
}
});
}
/**
* 更新進(jìn)度條與當(dāng)前播放時(shí)間
*
* @param {object} audio - audio對(duì)象
* @param {object} progressBar - 進(jìn)度條對(duì)象
* @param {object} progressDot - 進(jìn)度條控制點(diǎn)對(duì)象
* @param {object} audioCurTime - 當(dāng)前播放時(shí)間對(duì)象
*/
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);
}
/**
* 播放完成時(shí)把進(jìn)度調(diào)回開(kāi)始的位置
*
* @param {object} progressBar - 進(jìn)度條對(duì)象
* @param {object} progressDot - 進(jìn)度條控制點(diǎn)對(duì)象
* @param {object} audioCurTime - 當(dāng)前播放時(shí)間對(duì)象
* @param {object} playImg- 播放按鈕圖標(biāo)
* @param {object} pauseImg- 暫停按鈕圖標(biāo)
*/
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';
}
/**
* 音頻播放時(shí)間換算
*
* @param {number} value - 音頻當(dāng)前播放時(shí)間鸟廓,單位秒
*/
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;
}
/**
* 格式化時(shí)間顯示从祝,補(bǔ)零對(duì)齊
*
* 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復(fù)制粘貼沖突
上述幾個(gè)步驟完成后,插入音頻功能就已實(shí)現(xiàn)了引谜。但在編輯器中插入音頻后再粘貼其他內(nèi)容時(shí)牍陌,會(huì)發(fā)現(xiàn)音頻控件的進(jìn)度條樣式出了問(wèn)題。這是因?yàn)閡editor在粘貼后會(huì)對(duì)空內(nèi)容div進(jìn)行刪除员咽,而audio插件所插入的音頻控件中有空內(nèi)容div毒涧。可修改相應(yīng)方法將音頻控件中的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)
}
})
}
對(duì)刪除div的判定條件進(jìn)行修改,排除音頻控件的相關(guān)div:
if (domUtils.isEmptyBlock(node) && node.className != 'progress-bar-bg' && node.className != 'progressBar') {
domUtils.remove(node,true)
}
6. 文章頁(yè)面音頻控件初始化
在ueditor中編輯完文章內(nèi)容之后滑频,在展示文章時(shí)也需要對(duì)文章內(nèi)容中的音頻控件進(jìn)行初始化捡偏,故需要提供相應(yīng)的初始化方法供頁(yè)面調(diào)用。實(shí)現(xiàn)方式比較簡(jiǎn)單峡迷,和audio插件中的方法基本一致银伟,不詳述。
另若需支持移動(dòng)端的訪問(wèn)绘搞,則需要對(duì)點(diǎn)擊彤避、滑動(dòng)等事件進(jìn)行區(qū)分處理,對(duì)PC端訪問(wèn)要響應(yīng)mouse事件看杭,對(duì)移動(dòng)端訪問(wèn)要響應(yīng)touch事件:
// 定義不同端事件調(diào)用
var hasTouch = 'ontouchstart' in window,
startEvent = hasTouch ? 'touchstart' : 'mousedown',
moveEvent = hasTouch ? 'touchmove' : 'mousemove',
endEvent = hasTouch ? 'touchend' : 'mouseup',
cancelEvent = hasTouch ? 'touchcancel' : 'mouseup';
7. 效果圖
(1)Ueditor工具欄上音頻圖標(biāo)
(2)上傳音頻
(3)Ueditor內(nèi)傳入音頻后效果
(4)移動(dòng)端文章內(nèi)音頻效果