为什么寻找不能马上奏效
使用 Blob URL 时,您的音频播放器不会获得有关媒体持续时间的任何信息。我还发现你不能手动设置它。这将阻止您在本机浏览器音频控件的进度条上进行搜索。所以,不幸的是,我相信你不能使用本机控件。
什么是可能的解决方法?
您可以做的是测量录制会话持续多长时间,并将该持续时间传递给播放器控制器。该播放器控制器可以是现有的(例如HowlerJS),也可以是自定义的。现有的问题是它们中的大多数(全部?)不支持手动设置持续时间。如果您深入研究他们的代码,可能还有另一种解决方法,但就目前而言,我认为创建自定义播放器会很有趣。
自定义播放器
我创建了一个SoundClip
函数,该函数创建工作播放器的 DOM 元素,并允许您设置音频的 URL 及其持续时间(以秒为单位)。以下是如何使用它:
// Declare a new SoundClip instance
const audio = new SoundClip();
// Get its DOM player element and append it to some container
someContainer.appendChild(audio.getElement());
// Set the audio Url and duration
audio.setSource(audioURL, duration);
如何调整代码以使用它
首先,您必须测量录制发生所需的时间:
// At the top of your code, create an Object that will hold that data
const recordingTimes = {};
record.onclick = function() {
// Record the start time
recordingTimes.start = +new Date();
/* ... */
}
stop.onclick = function() {
// Record the end time
recordingTimes.end = +new Date();
// Calculate the duration in seconds
recordingTimes.duration = (recordingTimes.end - recordingTimes.start) / 1000;
/* ... */
}
然后,不使用audio
DOM 元素,而是使用SoundClip
实例:
mediaRecorder.onstop = function(e) {
/* ... */
const deleteButton = document.createElement('button');
// Declare a new SoundClip instance
const audio = new SoundClip();
/* ... */
// Append the SoundClip element to the DOM
clipContainer.appendChild(audio.getElement());
clipContainer.appendChild(clipLabel);
/* ... */
const audioURL = URL.createObjectURL(blob);
// Set the audio Url and duration
audio.setSource(audioURL, recordingTimes.duration);
/* ... */
}
进而?
然后,你应该能够做你想做的事。我在下面提供了SoundClip
函数和 CSS 的完整代码,但它非常基本而且不是很时尚。您可以决定对其进行自定义以满足您的需求,或者与市场上现有的播放器一起使用,请记住您必须破解它才能使其正常工作。
现场演示
https://shrt.tf/so_49886426/
完整代码
这在 StackOverflow 上不起作用,因为它不允许使用麦克风,但这里是完整的代码:
function SoundClip() {
const self = {
dom: {},
player: {},
class: 'sound-clip',
////////////////////////////////
// SoundClip basic functions
////////////////////////////////
// ======================
// Setup the DOM of the player and the player instance
// [Automatically called on instantiation]
// ======================
init: function() {
// == Create the DOM elements ==
// Wrapper
self.dom.wrapper = self.createElement('div', {
className: `${self.class} ${self.class}-disabled`
});
// Play button
self.dom.playBtn = self.createElement('div', {
className: `${self.class}-play-btn`,
onclick: self.toggle
}, self.dom.wrapper);
// Range slider
self.dom.progress = self.createElement('input', {
className: `${self.class}-progress`,
min: 0,
max: 100,
value: 0,
type: 'range',
onchange: self.onChange
}, self.dom.wrapper);
// Time and duration
self.dom.time = self.createElement('div', {
className: `${self.class}-time`,
innerHTML: '00:00 / 00:00'
}, self.dom.wrapper);
self.player.disabled = true;
// == Create the Audio player ==
self.player.instance = new Audio();
self.player.instance.ontimeupdate = self.onTimeUpdate;
self.player.instance.onended = self.stop;
return self;
},
// ======================
// Sets the URL and duration of the audio clip
// ======================
setSource: function(url, duration) {
self.player.url = url;
self.player.duration = duration;
self.player.instance.src = self.player.url;
// Enable the interface
self.player.disabled = false;
self.dom.wrapper.classList.remove(`${self.class}-disabled`);
// Update the duration
self.onTimeUpdate();
},
// ======================
// Returns the wrapper DOM element
// ======================
getElement: function() {
return self.dom.wrapper;
},
////////////////////////////////
// Player functions
////////////////////////////////
// ======================
// Plays or pauses the player
// ======================
toggle: function() {
if (!self.player.disabled) {
self[self.player.playing ? 'pause' : 'play']();
}
},
// ======================
// Starts the player
// ======================
play: function() {
if (!self.player.disabled) {
self.player.playing = true;
self.dom.playBtn.classList.add(`${self.class}-playing`);
self.player.instance.play();
}
},
// ======================
// Pauses the player
// ======================
pause: function() {
if (!self.player.disabled) {
self.player.playing = false;
self.dom.playBtn.classList.remove(`${self.class}-playing`);
self.player.instance.pause();
}
},
// ======================
// Pauses the player and resets its currentTime
// ======================
stop: function() {
if (!self.player.disabled) {
self.pause();
self.seekTo(0);
}
},
// ======================
// Sets the player's current time
// ======================
seekTo: function(sec) {
if (!self.player.disabled) {
self.player.instance.currentTime = sec;
}
},
////////////////////////////////
// Event handlers
////////////////////////////////
// ======================
// Called every time the player instance's time gets updated
// ======================
onTimeUpdate: function() {
self.player.currentTime = self.player.instance.currentTime;
self.dom.progress.value = Math.floor(
self.player.currentTime / self.player.duration * 100
);
self.dom.time.innerHTML = `
${self.formatTime(self.player.currentTime)}
/
${self.formatTime(self.player.duration)}
`;
},
// ======================
// Called every time the user changes the progress bar value
// ======================
onChange: function() {
const sec = self.dom.progress.value / 100 * self.player.duration;
self.seekTo(sec);
},
////////////////////////////////
// Utility functions
////////////////////////////////
// ======================
// Create DOM elements,
// assign them attributes and append them to a parent
// ======================
createElement: function(type, attributes, parent) {
const el = document.createElement(type);
if (attributes) {
Object.assign(el, attributes);
}
if (parent) {
parent.appendChild(el);
}
return el;
},
// ======================
// Formats seconds into [hours], minutes and seconds
// ======================
formatTime: function(sec) {
const secInt = parseInt(sec, 10);
const hours = Math.floor(secInt / 3600);
const minutes = Math.floor((secInt - (hours * 3600)) / 60);
const seconds = secInt - (hours * 3600) - (minutes * 60);
return (hours ? (`0${hours}:`).slice(-3) : '') +
(`0${minutes}:`).slice(-3) +
(`0${seconds}`).slice(-2);
}
};
return self.init();
}
if (navigator.mediaDevices) {
const record = document.querySelector('.record');
const stop = document.querySelector('.stop');
const soundClips = document.querySelector('.clips');
// Will hold the start time, end time and duration of recording
const recordingTimes = {};
const constraints = {
audio: true
};
let chunks = [];
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm'
});
record.onclick = function() {
// Record the start time
recordingTimes.start = +new Date();
mediaRecorder.start();
record.style.background = "red";
record.style.color = "black";
}
stop.onclick = function() {
// Record the end time
recordingTimes.end = +new Date();
// Calculate the duration in seconds
recordingTimes.duration = (recordingTimes.end - recordingTimes.start) / 1000;
mediaRecorder.stop();
record.style.background = "";
record.style.color = "";
}
mediaRecorder.onstop = function(e) {
const clipName = prompt('Enter a name for your sound clip');
const clipContainer = document.createElement('article');
const clipLabel = document.createElement('p');
const deleteButton = document.createElement('button');
// Declare a new SoundClip
const audio = new SoundClip();
clipContainer.classList.add('clip');
deleteButton.innerHTML = "Delete";
clipLabel.innerHTML = clipName;
// Append the SoundClip element to the DOM
clipContainer.appendChild(audio.getElement());
clipContainer.appendChild(clipLabel);
clipContainer.appendChild(deleteButton);
soundClips.appendChild(clipContainer);
const blob = new Blob(chunks);
chunks = [];
const audioURL = URL.createObjectURL(blob);
// Set the audio Url and duration
audio.setSource(audioURL, recordingTimes.duration);
deleteButton.onclick = function(e) {
evtTgt = e.target;
evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
}
}
mediaRecorder.ondataavailable = function(e) {
chunks.push(e.data);
}
})
.catch(function(err) {
console.log('The following error occurred: ' + err);
})
}
.sound-clip, .sound-clip * {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.sound-clip {
border: 1px solid #9ee0ff;
padding: .5em;
font-family: Arial, Helvetica, sans-serif;
}
.sound-clip.sound-clip-disabled {
opacity: .5;
}
.sound-clip-play-btn {
display: inline-block;
text-align: center;
width: 2em;
height: 2em;
border: 1px solid #12b2ff;
color: #12b2ff;
cursor: pointer;
vertical-align: middle;
margin-right: .5em;
transition: all .2s ease;
}
.sound-clip-play-btn:before {
content: "►";
line-height: 2em;
}
.sound-clip-play-btn.sound-clip-playing:before {
content: "❚❚";
line-height: 2em;
}
.sound-clip-play-btn:not(.sound-clip-disabled):hover {
background: #12b2ff;
color: #fff;
}
.sound-clip-progress {
line-height: 2em;
vertical-align: middle;
width: calc(100% - 3em);
}
.sound-clip-time {
text-align: right;
}
<button class="record">RECORD</button>
<button class="stop">STOP</button>
<div class="clips"></div>