سلام وقت بخیر من یک سیستم چت با livewire , laravel echo , pusher ایجاد کردم
حالا میخوام توی چت قابلیت ارسال ویس رو قرار بدم توسط webrtc
من به شکل زیر کدهامو نوشتم وقتی روی ضبط صدا میزنم به ظاهر ضبط انجام میشه ولی وقتی رو توقف میزنم هم ویس ارسال نمیشه تو چت هم یهو توی مرورگر همیچین خطایی نمایش میده
Error code: SBOX_FATAL_MEMORY_EXCEEDED
که مربوط به حافظه سیستم انگار که مطمئنا مشکل از کدنویسی من هست که باعث همچین خطایی شده و مربوط به سیستم نیست
فرم ارسال پیام و ویس
<form wire:submit.prevent="SendMessage" enctype="multipart/form-data">
<div class="d-flex">
<button type="submit" class="border-0 mt-2 me-3 fs-5 send-msg-button">
<i class="fa fa-paper-plane" aria-hidden="true"></i>
</button>
<input wire:model="body" type="text" class="form-control send-msg-input"
placeholder="پیام خود را بنویسید ...">
@if ($recording)
<div class="recording d-flex">
<span>در حال ضبط ...</span>
<button type="button" class="record-button border-0 mt-2 ms-3 fs-5"
wire:click.prevent="stopRecording">
<i class="fa fa-stop"></i>
</button>
</div>
@else
<button type="button" class="record-button border-0 mt-2 ms-3 fs-5"
wire:click.prevent="startRecording" name="voice">
<i class="fa fa-microphone"></i>
</button>
<input type="file" wire:model="recordingFile" accept="audio/*"
id="recordingFileInput" style="display:none">
@endif
</div>
</form>
component
public function SendMessage()
{
if ($this->body == null && $this->recordingFile == null) {
return null;
}
$message = Message::create([
'conversation_id' => $this->selectedConversation->id,
'sender_id' => Auth::id(),
'type' => $this->recordingFile ? 'audio' : 'text',
'content' => $this->recordingFile ? null : $this->body,
]);
if ($this->recordingFile) {
$response = Http::attach(
'recording',
file_get_contents($this->recordingFile->getRealPath()),
$this->recordingFile->getClientOriginalName()
)->post('/api/messages', [
'conversation_id' => $this->selectedConversation->id,
'sender_id' => Auth::id(),
'type' => 'audio',
]);
if ($response->ok()) {
$this->emitTo('chat.chatbox', 'messageSent', $message->id);
$this->emitTo('chat.chat-list', 'refresh');
} else {
session()->flash('error', 'Failed to send message.');
}
} else {
$this->emitTo('chat.chatbox', 'messageSent', $message->id);
$this->emitTo('chat.chat-list', 'refresh');
}
$this->reset(['body', 'recording', 'recordingFile']);
}
public function recordingDataAvailable($data)
{
$this->recordingFile = $data['file'];
}
public function startRecording()
{
$this->recording = true;
$this->emit('recordingStarted');
}
public function stopRecording()
{
if ($this->recordingFile) {
$filename = $this->recordingFile->getClientOriginalName();
$path = Storage::putFileAs('recordings', $this->recordingFile, $filename);
$message = Message::create([
'conversation_id' => $this->selectedConversation->id,
'sender_id' => Auth::id(),
'type' => 'audio',
'voice' => $path,
]);
$this->selectedConversation->last_time_message = $message->created_at;
$this->selectedConversation->save();
$this->emitTo('chat.chatbox', 'pushMessage', $message->id);
$this->emitTo('chat.chat-list', 'refresh');
$this->emitSelf('dispatchMessageSent');
}
$this->recordingFile = null;
$this->recording = false;
}
script
<script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
<script>
let mediaRecorder;
document.addEventListener('livewire:load', function() {
Livewire.on('recordingStarted', function() {
startRecording();
});
Livewire.on('recordingStopped', function() {
stopRecording();
});
});
function startRecording() {
navigator.mediaDevices.getUserMedia({
audio: true,
video: false
})
.then(stream => {
mediaRecorder = new MediaRecorder(stream);
let chunks = [];
mediaRecorder.start();
mediaRecorder.addEventListener('dataavailable', event => {
chunks.push(event.data);
});
mediaRecorder.addEventListener('stop', () => {
const blob = new Blob(chunks, {
type: 'audio/webm'
});
const url = URL.createObjectURL(blob);
const filename = new Date().toISOString() + '.webm';
const file = new File([blob], filename, {
type: 'audio/webm',
lastModified: Date.now()
});
this.emit('recordingDataAvailable', {
file: file
});
});
window.livewire.emit('recordingStarted', {});
})
.catch(error => {
console.error('Could not get user media: ', error);
});
}
function stopRecording() {
mediaRecorder
.stop();
window.livewire.emit('recordingStopped', {});
}
</script>
لطفا راهنمایی کنید خیلی مهم هست و خیلی وقته درگیرش هستم زیاد سرچ زدم ولی موفق نشدم
چون upload.php موجود نیست و شما باید اینجا آدرس فایلی که توی پروژتون باید اطلاعات به اون ارسال شه رو بدید.
همچنین منظور م این نبود که تمام let ها رو بکنید var.
یعنی به جای اینکه خارج از فانکشن ها بیاید تعریف بکنیدشون، بیاید داخل هر فانکشن تعریف بکنید.
مثلا :
function toggleRecording() {
var recordButton = document.getElementById('recordButton');
var stopButton = document.getElementById('stopButton');
باقی کد
.
.
.
}
برای تمامی فانکشن هایی که از این دو متغیر استفاده میکنند باید اینطوری براشون تعریف کنید.
با توجه به این موضوع هم دو متغیر let بالا هم که مربوط به اینها هستند حذف میشن
مشخصن میتونه مشکل از مرورگر هم باشه. مثلا نسخه اش قدیمی باشه و...
ولی در کل میتونید با کم کردن حجم فایل صوتی و یا چک کنید ببینید چه چیزی روی صفحه دارید لود میکنید که باعث افزایش مصرف حافظه میشه و یا هم با استفاده از یکسری سرویس های خارجی مثل Google Cloud و یا Amazon S3 و یا سرویس هایی که حالا میتونید پیدا کنید توی گوگل این محدودیت رو دور بزنید.
ولی در کل پیشنهاد من اینه که سعی کنید کدتون رو بهینه کنید و از ی روش دیگه برای ضبط ویس استفاده کنید، همچنین حتما چون پروژتون سنگینه دارید خیلی چیز ها روی صفحه لود میکنید، بهتره اونها رو هم چک کنید.
@MortezaVaezi
ممنونم از پاسختون
روش بهینه دیگه ای اگه هست ممنون میشم راهنمایی کنید
من خب دفعه اوله دارم مینویسم زیاد وارد نیستم .
مرورگرم اخرین نسخه هست و اپدیته
حقیقتن الان وقت کافی ندارم یک چیز تکمیل خدمتتون بدم.
ولی ی دمو از Chat GPT براتون گرفتم. به نظرم ایدش خوبه البته خب دمو هست باید بیاید روش خیلی کار کنید و کد رو مطابق میلتون اصلاح کنید.
HTML:
<div>
<button id="recordButton" onclick="toggleRecording()" disabled>شروع ضبط</button>
<button id="stopButton" onclick="stopRecording()" disabled>توقف ضبط</button>
</div>
<div>
<audio id="recordedAudio" controls></audio>
<button id="sendButton" onclick="sendAudio()">ارسال</button>
</div>
JavaScript:
let recordButton = document.getElementById('recordButton');
let stopButton = document.getElementById('stopButton');
let recordedAudio = document.getElementById('recordedAudio');
let sendButton = document.getElementById('sendButton');
let recorder;
let audioBlob;
function toggleRecording() {
if (recorder && recorder.state === 'recording') {
recorder.stopRecording();
stopButton.disabled = true;
recordButton.disabled = false;
} else {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(function(stream) {
recorder = RecordRTC(stream, { type: 'audio' });
recorder.startRecording();
stopButton.disabled = false;
recordButton.disabled = true;
})
.catch(function(error) {
console.error('Could not get user media: ', error);
});
}
}
function stopRecording() {
recorder.stopRecording(function() {
audioBlob = recorder.getBlob();
recordedAudio.src = URL.createObjectURL(audioBlob);
recordedAudio.play();
stopButton.disabled = true;
recordButton.disabled = false;
sendButton.disabled = false;
});
}
function sendAudio() {
let formData = new FormData();
formData.append('audioFile', audioBlob);
let xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.php', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// پردازش پاسخ دریافتی از سرور
console.log(xhr.responseText);
}
};
xhr.send(formData);
}
PHP :
<?php
if(isset($_FILES['audioFile'])) {
$file = $_FILES['audioFile'];
$fileName = $file['name'];
$fileTmpPath = $file['tmp_name'];
$fileSize = $file['size'];
$fileType = $file['type'];
// تنظیم مسیر ذخیره فایل صوتی
$uploadDirectory = 'audio/';
// اگر مسیر ذخیره وجود ندارد، آن را ایجاد میکنیم
if (!is_dir($uploadDirectory)) {
mkdir($uploadDirectory, 0755, true);
}
$uploadPath = $uploadDirectory . $fileName;
// ذخیره فایل صوتی در مسیر مورد نظر
if(move_uploaded_file($fileTmpPath, $uploadPath)) {
echo 'فایل با موفقیت آپلود شد.';
} else {
echo 'خطا در آپلود فایل.';
}
} else {
echo 'فایل صوتی دریافت نشد.';
}
?>
@MortezaVaezi
سلام وقت بخیر
متاسفانه این کدها توی livewire کمکی نکرد تغییرشون دادم ولی باز به ظاهر ضبط انجام میشه ولی ارسال نمیشه یا استپ نمیشه اصلا
@MortezaVaezi
نه ارورهای دیگه از
<script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
دوباره به چند روش نوشتم ولی خب کلا به جواب نرسیدم هنوز
@MortezaVaezi
کدهای html
<div>
<button id="recordButton" onclick="toggleRecording()">شروع ضبط</button>
<button id="stopButton" onclick="stopRecording()" disabled>توقف ضبط</button>
</div>
<div>
<audio id="recordedAudio" controls></audio>
<button id="sendButton" onclick="sendAudio()">ارسال</button>
</div>
اینجا برای شروع ضبط وقتی روی disable باشه که اصلا نمیشه چیزی ضبط کرد وقتی از دیزیبل خارج میکنم رکورد انجام میشه و دکمه توقف دیگه فعال نمیشه
کدهای جی اس
@push('scripts')
<script>
let recordButton = document.getElementById('recordButton');
let stopButton = document.getElementById('stopButton');
let recordedAudio = document.getElementById('recordedAudio');
let sendButton = document.getElementById('sendButton');
let recorder;
let audioBlob;
function toggleRecording() {
if (recorder && recorder.state === 'recording') {
recorder.stopRecording();
stopButton.disabled = true;
recordButton.disabled = false;
} else {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(function(stream) {
recorder = RecordRTC(stream, { type: 'audio' });
recorder.startRecording();
stopButton.disabled = false;
recordButton.disabled = true;
})
.catch(function(error) {
console.error('Could not get user media: ', error);
});
}
}
function stopRecording() {
recorder.stopRecording(function() {
audioBlob = recorder.getBlob();
recordedAudio.src = URL.createObjectURL(audioBlob);
recordedAudio.play();
stopButton.disabled = true;
recordButton.disabled = false;
sendButton.disabled = false;
});
}
function sendAudio() {
let formData = new FormData();
formData.append('audioFile', audioBlob);
let xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.php', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// پردازش پاسخ دریافتی از سرور
console.log(xhr.responseText);
}
};
xhr.send(formData);
}
</script>
@endpush
component
public function SendMessage()
{
if ($this->body == null && $this->recordingFile == null) {
return null;
}
$message = Message::create([
'conversation_id' => $this->selectedConversation->id,
'sender_id' => Auth::id(),
'type' => $this->recordingFile ? 'audio' : 'text',
'content' => $this->recordingFile ? null : $this->body,
]);
if (!$this->audioFile) {
session()->flash('error', 'فایل صوتی دریافت نشد.');
return;
}
$fileName = $this->audioFile->getClientOriginalName();
$filePath = Storage::disk('public')->putFileAs('audio', $this->audioFile, $fileName);
if ($filePath) {
session()->flash('message', 'فایل با موفقیت آپلود شد.');
} else {
session()->flash('error', 'خطا در آپلود فایل.');
}
// if ($this->recordingFile) {
// $response = Http::attach(
// 'recording',
// file_get_contents($this->recordingFile->getRealPath()),
// $this->recordingFile->getClientOriginalName()
// )->post('/api/messages', [
// 'conversation_id' => $this->selectedConversation->id,
// 'sender_id' => Auth::id(),
// 'type' => 'audio',
// ]);
// if ($response->ok()) {
// $this->emitTo('chat.chatbox', 'messageSent', $message->id);
// $this->emitTo('chat.chat-list', 'refresh');
// } else {
// session()->flash('error', 'Failed to send message.');
// }
// } else {
// $this->emitTo('chat.chatbox', 'messageSent', $message->id);
// $this->emitTo('chat.chat-list', 'refresh');
// }
$this->emitTo('chat.chatbox', 'messageSent');
$this->emitTo('chat.chat-list', 'refresh');
$this->reset('body');
}
خطای زیر توی کنسول دارم
RecordRTC version: 5.6.2
RecordRTC.js:63 started recording audio stream.
RecordRTC.js:1057 Using recorderType: MediaStreamRecorder
RecordRTC.js:2107 Passing following config over MediaRecorder API. Object
RecordRTC.js:713 Recorder state changed: recording
RecordRTC.js:103 Initialized recorderType: MediaStreamRecorder for output-type: audio
chat:432 Could not get user media: TypeError: Cannot set properties of null (setting 'disabled')
at chat:428:29
یه حرکتی بزن.
متغیر های
let recordButton = document.getElementById('recordButton');
let stopButton = document.getElementById('stopButton');
در هر تابع به عنوان متغیر همون تابع تعریف کن ببین چی میشه.
@MortezaVaezi
درست متوجه نشدم فک کنم
به این شکل منظورتونه ؟ تو ایت حالت خطایی ندارم ولی دکمه stop همچنان غیرفعال هست و قابلیت ارسال ویس هم نیست
@push('scripts')
<script>
let recordButton = document.getElementById('recordButton');
let stopButton = document.getElementById('stopButton');
let recordedAudio = document.getElementById('recordedAudio');
let sendButton = document.getElementById('sendButton');
let recorder;
let audioBlob;
function toggleRecording() {
if (recorder && recorder.state === 'recording') {
recorder.stopRecording();
// stopButton.disabled = true;
recordButton.disabled = false;
} else {
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(function(stream) {
recorder = RecordRTC(stream, {
type: 'audio'
});
recorder.startRecording();
// stopButton.disabled = true;
// recordButton.disabled = true;
})
.catch(function(error) {
console.error('Could not get user media: ', error);
});
}
}
function stopRecording() {
recorder.stopRecording(function() {
audioBlob = recorder.getBlob();
recordedAudio.src = URL.createObjectURL(audioBlob);
recordedAudio.play();
stopButton.disabled = true;
recordButton.disabled = false;
sendButton.disabled = false;
});
}
function sendAudio() {
let formData = new FormData();
formData.append('audioFile', audioBlob);
let xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.php', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// پردازش پاسخ دریافتی از سرور
console.log(xhr.responseText);
}
};
xhr.send(formData);
}
</script>
@endpush
به نظر میاد نمیتونه سلکتش کنه.
توی فانکشن وقتی میایی برابر false یا true فرارش میدی نمیتونه پیداش کنه.
اون رو تعریفشو با var کن به جای let و بیارش داخل خود تابع. ببین حل میشه یا نه
هرجا که let بود var قرار دادم الان ضبط میشه و باز دکمه توقف فعال نمیشه
وقتی هم روی دکمه ارسال میزنم یه خطایی میده که این قسمت رو من توجه نکردم به اسم فایل
chat:471 POST http://localhost:8000/upload.php 404 (Not Found)
اینجا توی کد من چطوری باید مسیر اپلود رو توی لاراول بهش بدم ؟
function sendAudio() {
var formData = new FormData();
formData.append('audioFile', audioBlob);
var xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.php', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// پردازش پاسخ دریافتی از سرور
console.log(xhr.responseText);
}
};
xhr.send(formData);
}
به جای upload.php نمیدونم چطوری مسیردهی کنم ، اینجا باید روت باشه یا مسیر فایل storage ؟
چون upload.php موجود نیست و شما باید اینجا آدرس فایلی که توی پروژتون باید اطلاعات به اون ارسال شه رو بدید.
همچنین منظور م این نبود که تمام let ها رو بکنید var.
یعنی به جای اینکه خارج از فانکشن ها بیاید تعریف بکنیدشون، بیاید داخل هر فانکشن تعریف بکنید.
مثلا :
function toggleRecording() {
var recordButton = document.getElementById('recordButton');
var stopButton = document.getElementById('stopButton');
باقی کد
.
.
.
}
برای تمامی فانکشن هایی که از این دو متغیر استفاده میکنند باید اینطوری براشون تعریف کنید.
با توجه به این موضوع هم دو متغیر let بالا هم که مربوط به اینها هستند حذف میشن
الان به این شکل نوشتم درسته ؟
@push('scripts')
<script>
var recorder;
var audioBlob;
function toggleRecording() {
var recordButton = document.getElementById('recordButton');
var stopButton = document.getElementById('stopButton');
if (recorder && recorder.state === 'recording') {
recorder.stopRecording();
stopButton.disabled = true;
recordButton.disabled = false;
} else {
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(function(stream) {
recorder = RecordRTC(stream, {
type: 'audio'
});
recorder.startRecording();
stopButton.disabled = true;
recordButton.disabled = true;
})
.catch(function(error) {
console.error('Could not get user media: ', error);
});
}
}
function stopRecording() {
var recordedAudio = document.getElementById('recordedAudio');
var sendButton = document.getElementById('sendButton');
recorder.stopRecording(function() {
audioBlob = recorder.getBlob();
recordedAudio.src = URL.createObjectURL(audioBlob);
recordedAudio.play();
stopButton.disabled = true;
recordButton.disabled = false;
sendButton.disabled = false;
});
}
function sendAudio() {
let formData = new FormData();
formData.append('_token', document.querySelector('meta[name="csrf-token"]').content);
formData.append('audioFile', audioBlob);
let xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:8000/chat/send', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// پردازش پاسخ دریافتی از سرور
console.log(xhr.responseText);
}
};
xhr.send(formData);
}
</script>
@endpush
form
<form wire:submit.prevent="SendMessage" enctype="multipart/form-data">
@csrf
<div class="d-flex">
<button type="submit" class="border-0 mt-2 me-3 fs-5 send-msg-button">
<i class="fa fa-paper-plane" aria-hidden="true"></i>
</button>
<input wire:model="body" type="text" class="form-control send-msg-input"
placeholder="پیام خود را بنویسید ...">
<div>
<button id="recordButton" onclick="toggleRecording()">شروع ضبط</button>
<button id="stopButton" onclick="stopRecording()" disabled>توقف ضبط</button>
</div>
<div>
<audio id="recordedAudio" controls></audio>
<button id="sendButton" onclick="sendAudio()">ارسال</button>
</div>
</div>
</form>
component
$message = Message::create([
'conversation_id' => $this->selectedConversation->id,
'sender_id' => Auth::id(),
'type' => $this->recordingFile ? 'audio' : 'text',
'content' => $this->recordingFile ? null : $this->body,
]);
if (!$this->audioFile) {
session()->flash('error', 'فایل صوتی دریافت نشد.');
return;
}
$fileName = $this->audioFile->getClientOriginalName();
$filePath = Storage::disk('public')->putFileAs('audio', $this->audioFile, $fileName);
if ($filePath) {
return response()->json(['status' => 'success']);
session()->flash('message', 'فایل با موفقیت آپلود شد.');
} else {
session()->flash('error', 'خطا در آپلود فایل.');
}
route
Route::get('/chat{key?}' , Main::class)->name('chat')->middleware('auth');
Route::post('/chat/send', Main::class);
اینجا دیگه اروری ندارم و برای دکمه توقف هم از حالت دیزیبل خارج کردم
<button id="stopButton" onclick="stopRecording()">توقف ضبط</button>
حالا روی توقف میزنم اکیه کار میکنه و صدای ضبط شده پخش میشه
فقط توی compenet و قسمتی که این کد رو نوشتم قطعا مشکل داره ، چون وقتی روی دکمه ارسال میزنم توی کنسول کل کدهای html رو نمایش میده
xhr.open('POST', 'http://localhost:8000/chat/send', true);
خب خداروشکر بقیش هم به شکل در یافت یک فایل از یک فرمه که از $FILES میتونید دریافتش کنید.
$file = $_FILES['audioFile'];
$fileName = $file['name'];
$fileTmpPath = $file['tmp_name'];
$fileSize = $file['size'];
$fileType = $file['type'];
باید فایل رو بگیرید و ذخیره کنید و...
توی دمویی که اول براتون فرستادم نمونش هست، از اون میتونید استفاده کنید.
والا من لاراول بلد نیستم.
ولی شاید این بتونه کمکتون بکنه.
use Illuminate\Http\Request;
Route::post('/upload', function (Request $request) {
if ($request->hasFile('file')) {
$file = $request->file('file');
// پردازش فایل و انجام عملیات مورد نیاز
// ...
// ذخیره فایل در محل مورد نظر
$file->store('public/uploads');
}
});
راستی اگر پاسخ منو به عنوان پاسخ برتر این گفتگو بزنید ممنون میشم
آیا مایل به ارسال نوتیفیکیشن و اخبار از طرف راکت هستید ؟