plivo-php
plivo-php copied to clipboard
How to Integrate Plivo SDK in Laravel?
I am integrating Plivo in Laravel to create a "click to call" function on my web app page. I want to provide features such as call, hangup, mute, and unmute on this page. I also want a feature that allows users to talk directly through the web. So, when the user clicks the "Call" button, the user can immediately speak to someone through the browser.
I have created codes like the following, but I get an error message: "Failed to load Plivo SDK: Plivo SDK failed to load".
show.blade.php
@extends('layouts.app')
@section('title', "Contact: {$contact->first_name} {$contact->last_name}")
@section('styles')
<script type="text/javascript" src="https://cdn.plivo.com/sdk/browser/v2/plivo.min.js"> </script>
<style>
.call-interface {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 20px;
}
.call-status {
font-size: 18px;
margin-bottom: 15px;
}
.call-controls button {
margin-right: 10px;
margin-bottom: 10px;
}
.call-timer {
font-size: 24px;
font-weight: bold;
margin: 15px 0;
}
.call-logs {
margin-top: 30px;
}
</style>
@endsection
@section('content')
<div class="col-md-4">
<div class="card mb-3">
<div class="card-header">
<h5 class="card-title mb-0">
Contact Details
</h5>
</div>
<div class="card-body">
<div class="mb-1">
<strong>Name:</strong> {{ $contact->first_name }} {{ $contact->last_name }}
</div>
<div class="mb-1">
<strong>Phone:</strong> {{ $contact->phone }}
</div>
</div>
<div class="card-footer">
<div class="row g-3 align-items-center">
<div class="col-12">
<label for="note" class="form-label">Note</label>
<textarea name="note" class="form-control" id="note" rows="3">{!! $disposition ? $disposition->note : '' !!}</textarea>
</div>
<div class="col-auto">
<select name="disposition" class="form-select" id="disposition">
<option value="">Select disposition</option>
@foreach (config('data.disposition_status') as $key => $item)
<option value="{{ $key }}" {{ $disposition && $disposition->status === $key ? 'selected' : '' }}>{{ $item }}</option>
@endforeach
</select>
</div>
<div class="col-auto">
<button onclick="makeCall('{{ $contact->phone }}')" class="btn btn-dark">Call</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3>Call Interface</h3>
</div>
<div class="card-body">
<div class="call-interface">
<div id="call-status" class="call-status alert alert-info">
Ready
</div>
<div class="call-timer" id="call-timer">
00:00:00
</div>
<div class="call-controls">
<button id="makeCall" class="btn btn-success" disabled>
<i class="fas fa-phone"></i> Call
</button>
<button id="hangupCall" class="btn btn-danger" disabled>
<i class="fas fa-phone-slash"></i> Hangup
</button>
<button id="muteCall" class="btn btn-warning" disabled>
<i class="fas fa-microphone-slash"></i> Mute
</button>
<button id="unmuteCall" class="btn btn-info" disabled>
<i class="fas fa-microphone"></i> Unmute
</button>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
let client;
let currentCall = null;
let callTimer = null;
let callStartTime = null;
async function initializePlivoClient() {
try {
console.log('Initializing Plivo client...');
if (typeof window.plivoBrowserSdk === 'undefined') {
throw new Error('Plivo Browser SDK not loaded');
}
const response = await fetch('{{ route("plivo.token") }}');
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
console.log('Token received, initializing client...');
try {
const options = {
debug: "DEBUG",
permOnClick: true,
enableTracking: true,
audioConstraints: {
optional: [
{ echoCancellation: true },
{ noiseSuppression: true }
]
}
};
// Create client using the correct SDK reference
client = new window.plivoBrowserSdk.Client(options);
console.log('Client created, attempting login...');
// Login
await client.login(data.username, data.password);
console.log('Login successful');
document.getElementById('makeCall').disabled = false;
document.getElementById('call-status').innerHTML = 'Ready to call';
document.getElementById('call-status').className = 'alert alert-success';
setupEventListeners();
} catch (error) {
throw new Error(`Client initialization failed: ${error.message}`);
}
} catch (error) {
console.error('Failed to initialize Plivo client:', error);
document.getElementById('call-status').innerHTML = 'Failed to initialize phone: ' + error.message;
document.getElementById('call-status').className = 'alert alert-danger';
}
}
// Wait for SDK to load
function waitForPlivoSDK() {
return new Promise((resolve, reject) => {
let attempts = 0;
const maxAttempts = 20;
const checkSDK = setInterval(() => {
if (typeof window.plivoBrowserSdk !== 'undefined') {
clearInterval(checkSDK);
resolve();
} else {
attempts++;
if (attempts >= maxAttempts) {
clearInterval(checkSDK);
reject(new Error('Plivo SDK failed to load'));
}
}
}, 250);
});
}
// Initialize when document is ready
document.addEventListener('DOMContentLoaded', async () => {
try {
await waitForPlivoSDK();
await initializePlivoClient();
} catch (error) {
console.error('Initialization failed:', error);
document.getElementById('call-status').innerHTML = 'Failed to load Plivo SDK: ' + error.message;
document.getElementById('call-status').className = 'alert alert-danger';
}
});
function setupEventListeners() {
if (!client) {
console.error('Client not initialized');
return;
}
client.on('onLogin', () => {
console.log('Successfully logged in to Plivo');
document.getElementById('call-status').innerHTML = 'Ready to make calls';
document.getElementById('call-status').className = 'alert alert-success';
});
client.on('onLoginFailed', (error) => {
console.error('Login failed:', error);
});
client.on('onCallRemoteRinging', () => {
document.getElementById('call-status').innerHTML = 'Ringing...';
document.getElementById('call-status').className = 'alert alert-warning';
});
client.on('onCallAnswered', () => {
document.getElementById('call-status').innerHTML = 'Call in progress';
document.getElementById('call-status').className = 'alert alert-success';
document.getElementById('hangupCall').disabled = false;
document.getElementById('muteCall').disabled = false;
startCallTimer();
});
client.on('onCallTerminated', () => {
resetCallInterface();
logCall('completed');
});
client.on('onCallFailed', (error) => {
console.error('Call failed:', error);
resetCallInterface();
logCall('failed');
});
}
function startCallTimer() {
callStartTime = new Date();
callTimer = setInterval(() => {
const now = new Date();
const diff = new Date(now - callStartTime);
const hours = diff.getUTCHours().toString().padStart(2, '0');
const minutes = diff.getUTCMinutes().toString().padStart(2, '0');
const seconds = diff.getUTCSeconds().toString().padStart(2, '0');
document.getElementById('call-timer').innerHTML = `${hours}:${minutes}:${seconds}`;
}, 1000);
}
function resetCallInterface() {
currentCall = null;
document.getElementById('call-status').innerHTML = 'Ready';
document.getElementById('call-status').className = 'alert alert-info';
document.getElementById('hangupCall').disabled = true;
document.getElementById('muteCall').disabled = true;
document.getElementById('unmuteCall').disabled = true;
document.getElementById('call-timer').innerHTML = '00:00:00';
if (callTimer) {
clearInterval(callTimer);
callTimer = null;
}
callStartTime = null;
}
async function logCall(status) {
try {
await fetch('/api/call-logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
contact_id: '{{ $contact->id }}',
status: status,
start_time: callStartTime,
end_time: new Date(),
duration: callStartTime ? Math.round((new Date() - callStartTime) / 1000) : 0
})
});
} catch (error) {
console.error('Failed to log call:', error);
}
}
// Initialize when page loads
document.addEventListener('DOMContentLoaded', initializePlivoClient);
// Call button event listener
document.getElementById('makeCall').addEventListener('click', async () => {
try {
currentCall = await client.call('{{ $contact->phone }}');
document.getElementById('call-status').innerHTML = 'Calling...';
document.getElementById('call-status').className = 'alert alert-warning';
} catch (error) {
console.error('Error making call:', error);
document.getElementById('call-status').innerHTML = 'Call failed: ' + error.message;
document.getElementById('call-status').className = 'alert alert-danger';
}
});
// Hangup button event listener
document.getElementById('hangupCall').addEventListener('click', () => {
if (currentCall) {
currentCall.hangup();
}
});
// Mute button event listener
document.getElementById('muteCall').addEventListener('click', () => {
if (currentCall) {
currentCall.mute();
document.getElementById('muteCall').disabled = true;
document.getElementById('unmuteCall').disabled = false;
}
});
// Unmute button event listener
document.getElementById('unmuteCall').addEventListener('click', () => {
if (currentCall) {
currentCall.unmute();
document.getElementById('muteCall').disabled = false;
document.getElementById('unmuteCall').disabled = true;
}
});
</script>
@endpush
PlivoController.php
<?php
namespace App\Http\Controllers;
use App\Models\Call;
use Plivo\RestClient;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
class PlivoController extends Controller
{
protected $client;
public function __construct()
{
$this->client = new RestClient(
config('services.plivo.auth_id'),
config('services.plivo.auth_token')
);
}
public function getToken()
{
try {
$endpointId = config('services.plivo.endpoint_id');
$username = 'user_' . time();
$token = $this->generateToken($username);
return response()->json([
'username' => $username,
'password' => $token
]);
} catch (\Exception $e) {
\Log::error('Plivo Token Error', [
'error' => $e->getMessage()
]);
return response()->json([
'error' => $e->getMessage()
], 500);
}
}
private function generateToken($username)
{
$authId = config('services.plivo.auth_id');
$authToken = config('services.plivo.auth_token');
$iat = time();
$exp = $iat + 3600; // Token berlaku 1 jam
$payload = [
"iss" => $authId,
"sub" => $username,
"iat" => $iat,
"exp" => $exp,
"jti" => Str::uuid()->toString()
];
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(json_encode($payload)));
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $authToken, true);
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
}
public function handleCallEvent(Request $request)
{
\Log::info('Call Event Received', $request->all());
return response()->json(['status' => 'success']);
}
}
web.php
Route::get('/plivo/token', [PlivoController::class, 'getToken'])->name('plivo.token');
Route::post('/plivo/call-event', [PlivoController::class, 'handleCallEvent'])->name('plivo.call-event');
What is the correct way to integrate Plivo in Laravel?