«Допиливаем» Asterisk CDR Viewer под себя

    image

    «Я профессионал, потому что не ленюсь искать информацию в google» — сказал мне однажды коллега.

    А я поленился и начал «допиливать» CDR Viewer под себя, даже не посмотрев хотя бы вот это.
    А может и не в лени дело, просто было интересно… в общем, что из этого вышло можно посмотреть под катом:)


    Споры о том, что лучше использовать в качестве офисной АТС — asterisk (с веб-интерфейсом или без, хотя это отдельная тема для споров) или какую-то коробку типа Panasonic, которых на рынок выкинуто немеренное количество — не утихают до сих пор, но топик не об этом, лично для себя я уже давно определился. Хотелось бы поделиться с сообществом своим вариантом придания интерфейсу просмотра статистики дополнительного фунционала.

    В качестве «подопытного» я использовал FreePBX Distro (FreePBX 2.11, Asterisk 11, CentOS 6.5), скачанный с официального сайта проекта. Выбор был продиктован тем, что разработчики FreePBX уже позаботились о прикручивании БД к Asterisk и структура хранения записей в общем-то меня устраивает. Хотя процедура «прикручивания» MySQL или какой-либо другой базы к Asterisk была описана ни раз и ни два, о чем можно почитать например здесь, все же в целях экономии времени я решил этого не делать.

    За основу был взят Asterisk CDR Viewer (если не нужно каких-то сверхмудреных отчетов — то вполне себе пригодная и простенькая статистика), скачать можно тут.

    Установка CDR Viewer не представляется какой-то нетривиальной задачей.

    Переходим в нужную нам директорию, качаем архив, извлекаем файлы из архива:

    cd /var/www
    wget https://asterisk-cdr-viewer.googlecode.com/files/asterisk-cdr-viewer-1.0.2.tgz
    tar -xzvf asterisk-cdr-viewer-1.0.2.tgz
    


    Переносим файлик алиаса в папку с apache2:
    cp /var/www/html/asterisk-cdr-viewer/contrib/httpd/asterisk-cdr-viewer.conf /etc/apache2/conf.d/asterisk-cdr-viewer.conf
    


    Изменяем настройки подключения к БД для Asterisk-CDR-viewer

    cd /var/www/asterisk-cdr-viewer/include/
    vim config.inc.php
    


    Нужно поменять параметры в соответствии с текущей конфигурацией вашей базы:

    $db_user = '[MySQL пользователь]';
    $db_pass = '[MySQL пароль]';
    $db_name = '[Имя базы]';

    Делаем рестарт веб-сервера:

    service apache2 restart
    


    Теперь в браузере набирая [адрес asteridk-сервера]/acdr/ попадаем на страницу статистики.

    Первое, что мне захотелось сделать — прикрутить авторизацию для просмотра этой самой статистики, для этого воспользуемся htpasswd.
    Если не установлена —
    aptitude install apache2-utils
    


    Переходим в /etc/apache2 и созадем юзер/пароль для статистики:
    htpasswd -c passwordfile username
    


    Вводим пароль в диалоге, который предлагает htpasswd и получаем файл «passwordfile» с юзером «username» и сгенерированным зашифрованным паролем.

    Далее в /etc/apache2/conf.d изменяем asterisk-cdr-viewer.conf, раскомментрировав строки авторизации, в результате получаем:

    Alias /acdr/ "/var/www/asterisk-cdr-viewer/"

    <Location "/acdr/">
    <------>AuthName «Asterisk-CDR-Stat»
    <------>AuthType Basic
    <------>AuthUserFile /etc/apache2/passwordfile
    <------>AuthGroupFile /dev/null
    <------>require valid-user


    Рестартуем apache2 и при входе на страницу видим окно авторизации:

    image

    Следующее, что был сделано — это прослушивание разговоров из веб-интерфейса.

    1) Для прослушивания звонков добавляем две иконки в каталог /var/www/asterisk-cdr-viewer/templates/images (play и stop)

    2) Добавляем в /var/www/asterisk-cdr-viewer/templates/header.tpl.php объявление js-функции для оптимизации производительности;

    В результате получаем:

    <head>
    	<title>Asterisk Call Detail Records</title>
    	<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
    	<link rel="stylesheet" href="style/screen.css" type="text/css" media="screen" />
    	<link rel="shortcut icon" href="templates/images/favicon.ico" />
    	<script type="text/javascript"/>
    function audioPreview(e,cnc) {
     var uri = e.attributes.getNamedItem('data-uri').value;
     var audioElement;
     // 1 if not exists audio control, then create it:
     if (!(audioElement = document.getElementById('au_preview'))) {
      audioElement = document.createElement('audio');
      audioElement.id = 'au_preview';
      audioElement.controls = true;
      audioElement.style.display = 'none';
      document.body.appendChild(audioElement);
     }
     else {
      // 2 need to stop and hide if playing:
      var prevIcon = audioElement.parentNode.previousSibling;
      prevIcon.src = 'templates/images/play.png';
      prevIcon.onclick = function(){ return audioPreview(prevIcon,false);};
     }
    
     if (('undefined'===typeof cnc)||(!cnc)) {
      //1. to show
      e.nextSibling.appendChild(audioElement);
      audioElement.src = uri;
      audioElement.style.display = 'block';
      audioElement.play();
      e.onclick = function(){ return audioPreview(e,true); };
      e.src = 'templates/images/stop.png';
     }
     else {
      //2. to hide
      audioElement.pause();
      audioElement.style.display = 'none';
      e.onclick = function(){ return audioPreview(e,false); };
      e.src = 'templates/images/play.png';
     }
    }</script>
    </head>
    <body>
    	<table id="header">
    		<tr>
    			<td id="header_logo" rowspan="2" align="left"><a href="/" title="Home"><img src="" alt="Asterisk CDR Viewer" /></a></td>
    			<td id="header_title">Asterisk CDR Viewer</td>
    			<td align='right'>
    				<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=XVUVZY5D922JJ&lc=RU&item_name=i%2eo%2e&item_number=asterisk%2dcdr¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="http://habrastorage.org/getpro/habr/post_images/92a/7cd/b91/92a7cdb914a8e942ca23d0ec49367cc3.gif" align="center"/></a>
    			</td>
    		</tr>
    		<tr>
    		<td id="header_subtitle"> </td>
    			<td align='right'>
    			<?php
    			if ( strlen(getenv('REMOTE_USER')) ) {
    				echo "<a href='/acdr/index.php?action=logout'>logout: ". getenv('REMOTE_USER') ."</a>";
    			}
    			?>
    		</td>
    		</tr>
    		</table>
    



    3)
    Изменяем /var/www/asterisk-cdr-viewer/include/functions.inc.php, модифицируя конструкции echo: к иконке динамика добавляем небольшой кусочек необходимой разметки
    <head>
    	<title>Asterisk Call Detail Records</title>
    	<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
    	<link rel="stylesheet" href="style/screen.css" type="text/css" media="screen" />
    	<link rel="shortcut icon" href="templates/images/favicon.ico" />
    	<script type="text/javascript"/>
    function audioPreview(e,cnc) {
     var uri = e.attributes.getNamedItem('data-uri').value;
     var audioElement;
     // 1 if not exists audio control, then create it:
     if (!(audioElement = document.getElementById('au_preview'))) {
      audioElement = document.createElement('audio');
      audioElement.id = 'au_preview';
      audioElement.controls = true;
      audioElement.style.display = 'none';
      document.body.appendChild(audioElement);
     }
     else {
      // 2 need to stop and hide if playing:
      var prevIcon = audioElement.parentNode.previousSibling;
      prevIcon.src = 'templates/images/play.png';
      prevIcon.onclick = function(){ return audioPreview(prevIcon,false);};
     }
    
     if (('undefined'===typeof cnc)||(!cnc)) {
      //1. to show
      e.nextSibling.appendChild(audioElement);
      audioElement.src = uri;
      audioElement.style.display = 'block';
      audioElement.play();
      e.onclick = function(){ return audioPreview(e,true); };
      e.src = 'templates/images/stop.png';
     }
     else {
      //2. to hide
      audioElement.pause();
      audioElement.style.display = 'none';
      e.onclick = function(){ return audioPreview(e,false); };
      e.src = 'templates/images/play.png';
     }
    }</script>
    </head>
    <body>
    	<table id="header">
    		<tr>
    			<td id="header_logo" rowspan="2" align="left"><a href="/" title="Home"><img src="" alt="Asterisk CDR Viewer" /></a></td>
    			<td id="header_title">Asterisk CDR Viewer</td>
    			<td align='right'>
    				<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=XVUVZY5D922JJ&lc=RU&item_name=i%2eo%2e&item_number=asterisk%2dcdr¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="" align="center"/></a>
    			</td>
    		</tr>
    		<tr>
    		<td id="header_subtitle"> </td>
    			<td align='right'>
    			<?php
    			if ( strlen(getenv('REMOTE_USER')) ) {
    				echo "<a href='/acdr/index.php?action=logout'>logout: ". getenv('REMOTE_USER') ."</a>";
    			}
    			?>
    		</td>
    		</tr>
    		</table>
    root@sip:/var/www/asterisk-cdr-viewer/templates# 
    root@sip:/var/www/asterisk-cdr-viewer/include# cat functions.inc.php
    <?php
    
    /* Recorded file */
    function formatFiles($row) {
    	global $system_monitor_dir, $system_fax_archive_dir, $system_audio_format, $system_arch_audio_format;
    
    	/* File name formats, please specify: */
    	
    	/* 
    		caller-called-timestamp.wav 
    	*/
    	/* 
    	$recorded_file = $row['src'] .'-'. $row['dst'] .'-'. $row['call_timestamp']
    	*/
    	/* ============================================================================ */	
    
    	/* 
    		ends at the uniqueid.wav, for example: 
    												date-time-uniqueid.wav 
    	
    		thanks to Beto Reyes
    	*/
    	/*
    	$recorded_file = glob($system_monitor_dir . '/*' . $row['uniqueid'] . '.' . $system_audio_format);
    	if (count($recorded_file)>0) {
    		$recorded_file = basename($recorded_file[0],".$system_audio_format");
    	} else {
    		$recorded_file = $row['uniqueid'];
    	}
    	*/
    	/* ============================================================================ */
    
    	/*      This example for multi-directory archive without uniqueid, named by format:
    			<date>/<time>_<caller>-<destination>.<filetype>
    
    			example: (tree /var/spool/asterisk/monitor)
    
    		|-- 2012.09.12
    		|   |-- 10-37_4952704601-763245.ogg
    		|   `-- 10-43_106-79236522173.ogg
    		`-- 2012.09.13
    			|-- 11-42_101-79016410692.ogg
    			|-- 12-43_104-671554.ogg
    			`-- 15-49_109-279710.ogg
    
    		Added by BAXMAH (pcm@ritm.omsk.ru)
    	*/
    	/*
    	   $record_datetime = DateTime::createFromFormat('Y-m-d G:i:s', $row['calldate']);
    
    	   $recorded_file = date_format($record_datetime, 'Y.m.d/G-i') .'_'. $row['src'] .'-'. $row['dst'];
    	*/
    	/* ============================================================================ */
    
    	/*
    		This is a multi-dir search script for filenames like "/var/spool/asterisk/monitor/dir1/dir2/dir3/*uniqueid*.*"
    		Doesn't matter, WAV, MP3 or other file format, only UNIQID  is  required at the end of the filename 
    		;---------------------------------------------------------------------------  
    	   example: (tree /var/spool/asterisk/monitor)
    
        |-- in
        |   |-- 4951234567
        |   |   `-- 20120101_234231_4956401234_to_74951234567_1307542950.0.wav
        |   `-- 4997654321
        |       `-- 20120202_234231_4956401234_to_74997654321_1303542950.0.wav
        `-- out
            |-- msk
            |   `-- 20120125_211231_4956401234_to_74951234567_1307542950.0.wav
            `-- region
                `-- 20120112_211231_4956405570_to_74952210533_1307542950.0.wav
    
          6 directories, 4 files
    		;----------------------------------------------------------------------------
    	   added by Dein admin@sadmin.ru         
    	*/
    	
    	
    	//************ Get a list of subdirectories as array to search by glob function  **************
    	if (!function_exists('get_dir_list')) {
    		function get_dir_list($dir){
    			global $dirlist;			
    			$dirlist=array();
    			if (!function_exists('find_dirs_recursive')) {
    				function find_dirs_recursive($sdir) {
    					global $dirlist;
    					foreach(glob($sdir) as $filename) {
    						//echo $filename;
    						if(is_dir($filename)) {
    							$dirlist[]=$filename;
    							find_dirs_recursive($filename."/*");
    						};//endif
    					};//endforeach
    				}; //endfunc                                                                                               
    			};//endif exists
    			find_dirs_recursive($dir."/*");
    		};//endfunc
    	}
    
    	//*************** Main function  ************
    	if (!function_exists('find_record_by_uniqid')) {
    		function find_record_by_uniqid($path,$uniqid){
    			global $dirlist;
    			if (sizeof($dirlist) == 0 ){
    				get_dir_list($path);
    			};//endif size==0
    
    			if (sizeof($dirlist) == 0 ) {return "SOME ERROR, dirlist is empty";};
    
    			$found = "NOTHING FOUND";
    			foreach ($dirlist as $curdir) {
    				$res=glob($curdir."/*".$uniqid.".*");
    				if ($res) {$found=$res[0]; break;};
    			};//endforeach
    
    			$res=str_replace($path,"",$found);	//cut $path from full filename 
    			
    			return $res;			//to be compartable with func. formatFiles($row)
    
    		};//endfunc
    	}
    	
    	$recorded_file = find_record_by_uniqid($system_monitor_dir,$row['uniqueid']);
    	
    	
    	/* ============================================================================ */
    
    	/* 
    		uniqueid.wav 
    	*/
    //	$recorded_file = $row['filename'];
    	/* ============================================================================ */	
    
    	if (file_exists("$system_monitor_dir/$recorded_file.$system_audio_format")) {
    		// insert here:
    		echo "    <td class=\"record_col\"><div class=\"record_ctrl\"><a href=\"download.php?audio=$recorded_file.$system_audio_format\" title=\"Listen to call recording\"><img src=  recording\" /></a><img class=\"record_preview_icon\" src=  onclick=\"audioPreview(this,false);\"/><div class=\"record_preview_lt\"></div></div></td>\n";
    	} elseif ( isset($system_arch_audio_format) and file_exists("$system_monitor_dir/$recorded_file.$system_audio_format.$system_arch_audio_format")) {
    		echo "    <td class=\"record_col\"><a href=\"download.php?arch=$recorded_file.$system_audio_format.$system_arch_audio_format\" title=\"Download archive\"><img src=  recording\" /></a></td>\n";
    	} elseif (file_exists("$system_fax_archive_dir/$recorded_file.tif")) {
    		echo "    <td class=\"record_col\"><a href=\"download.php?fax=$recorded_file.tif\" title=\"View FAX image\"><img src=  image\" /></a></td>\n";
    	} elseif (file_exists("$system_monitor_dir/$recorded_file")) 
    {		// insert here:
    		echo "    <td class=\"record_col\"><div class=\"record_ctrl\"><a href=\"download.php?audio=$recorded_file\" title=\"Listen to call recording\"><img src=  recording\" /></a><img class=\"record_preview_icon\" src=  onclick=\"audioPreview(this,false);\"/><div class=\"record_preview_lt\"></div></div></td>\n";
    	} else {
    		echo "    <td class=\"record_col\"></td>\n";
    	}
    }
    
    /* CDR Table Display Functions */
    function formatCallDate($calldate,$uniqueid) {
    	echo "    <td class=\"record_col\"><abbr title=\"UniqueID: $uniqueid\">$calldate</abbr></td>\n";
    }
    
    function formatChannel($channel) {
    	$trunk_name = preg_replace('/(.*)-[^-]+$/','$1',$channel);
    	echo "    <td class=\"record_col\"><abbr title=\"Channel: $channel\">$trunk_name</abbr></td>\n";
    }
    
    function formatClid($clid) {
    	$clid_only = explode(' <', $clid, 2);
    	$clid = htmlspecialchars($clid_only[0]);
    	echo "    <td class=\"record_col\">$clid</td>\n";
    }
    
    function formatSrc($src,$clid) {
    	if (empty($src)) {
    		echo "    <td class=\"record_col\">UNKNOWN</td>\n";
    	} else {
    		$src = htmlspecialchars($src);
    		$clid = htmlspecialchars($clid);
    		echo "    <td class=\"record_col\"><abbr title=\"Caller*ID: $clid\">$src</abbr></td>\n";
    	}
    }
    
    function formatApp($app, $lastdata) {
    	echo "    <td class=\"record_col\"><abbr title=\"Application: $app($lastdata)\">$app</abbr></td>\n";
    }
    
    function formatDst($dst, $dcontext) {
    	global $rev_lookup_url;
    	if (strlen($dst) == 11 and strlen($rev_lookup_url) > 0 ) {
    		$rev = str_replace('%n', $dst, $rev_lookup_url);
    		echo "    <td class=\"record_col\"><abbr title=\"Destination Context: $dcontext\"><a href=\"$rev\" target=\"reverse\">$dst</a></abbr></td>\n";
    	} else {
    		echo "    <td class=\"record_col\"><abbr title=\"Destination Context: $dcontext\">$dst</abbr></td>\n";
    	}
    }
    
    function formatDisposition($disposition, $amaflags) {
    	switch ($amaflags) {
    		case 0:
    			$amaflags = 'DOCUMENTATION';
    			break;
    		case 1:
    			$amaflags = 'IGNORE';
    			break;
    		case 2:
    			$amaflags = 'BILLING';
    			break;
    		case 3:
    		default:
    			$amaflags = 'DEFAULT';
    	}
    	echo "    <td class=\"record_col\"><abbr title=\"AMA Flag: $amaflags\">$disposition</abbr></td>\n";
    }
    
    function formatDuration($duration, $billsec) {
    	$duration = sprintf('%02d', intval($duration/60)).':'.sprintf('%02d', intval($duration%60));
    	$billduration = sprintf('%02d', intval($billsec/60)).':'.sprintf('%02d', intval($billsec%60));
    	echo "    <td class=\"record_col\"><abbr title=\"Billing Duration: $billduration\">$duration</abbr></td>\n";
    }
    
    function formatUserField($userfield) {
    	echo "    <td class=\"record_col\">$userfield</td>\n";
    }
    
    function formatAccountCode($accountcode) {
    	echo "    <td class=\"record_col\">$accountcode</td>\n";
    }
    
    /* Asterisk RegExp parser */
    function asteriskregexp2sqllike( $source_data, $user_num ) {
    	$number = $user_num;
    	if ( strlen($number) < 1 ) {
    		$number = $_REQUEST[$source_data];
    	}
    	if ( '__' == substr($number,0,2) ) {
    		$number = substr($number,1);
    	} elseif ( '_' == substr($number,0,1) ) {
    		$number_chars = preg_split('//', substr($number,1), -1, PREG_SPLIT_NO_EMPTY);
    		$number = '';
    		foreach ($number_chars as $chr) {
    			if ( $chr == 'X' ) {
    				$number .= '[0-9]';
    			} elseif ( $chr == 'Z' ) {
    				$number .= '[1-9]';
    			} elseif ( $chr == 'N' ) {
    				$number .= '[2-9]';
    			} elseif ( $chr == '.' ) {
    				$number .= '.+';
    			} elseif ( $chr == '!' ) {
    				$_REQUEST[ $source_data .'_neg' ] = 'true';
    			} else {
    				$number .= $chr;
    			}
    		}
    		$_REQUEST[ $source_data .'_mod' ] = 'asterisk-regexp';
    	}
    	return $number;
    }
    
    /* empty() wrapper. Thanks to Mikael Carlsson. */
    function is_blank($value) {
    	return empty($value) && !is_numeric($value);
    }
    
    /* 
    	Money format
    
    	thanks to Shiena Tadeo
    */ 
    function formatMoney($number, $cents = 2) { // cents: 0=never, 1=if needed, 2=always
    	global $callrate_currency;
    	if (is_numeric($number)) { // a number
    		if (!$number) { // zero
    			$money = ($cents == 2 ? '0.00' : '0'); // output zero
    		} else { // value
    			if (floor($number) == $number) { // whole number
    				$money = number_format($number, ($cents == 2 ? 2 : 0)); // format
    			} else { // cents
    				$money = number_format(round($number, 2), ($cents == 0 ? 0 : 2)); // format
    			} // integer or decimal
    		} // value
    		echo   "<td class=\"chart_data\">$callrate_currency<span>$money</span></td>\n";
    	} else {
    		echo   "<td class=\"chart_data\"> </td>\n";
    	}
    } // formatMoney
    
    /* 
    	CallRate
    	return callrate array [ areacode, rate, description, bill type, total_rate] 
    */
    function callrates($dst,$duration,$file) {
    	global $callrate_csv_file, $callrate_cache;
    
    	if ( strlen($file) == 0 ) {
    		$file = $callrate_csv_file;
    		if ( strlen($file) == 0 ) {
    			return array('','','','','');
    		}
    	}
    	
    	if ( ! array_key_exists( $file, $callrate_cache ) ) {
    		$callrate_cache[$file] = array();
    		$fr = fopen($file, "r") or die("Can not open callrate file ($file).");
    		while(($fr_data = fgetcsv($fr, 1000, ",")) !== false) {
    			$callrate_cache[$file]["$fr_data[0]"] = array( $fr_data[1], $fr_data[2], $fr_data[3] );
    		}
    		fclose($fr);
    	}
    
    	for ( $i = strlen($dst); $i > 0; $i-- ) {
    		if ( array_key_exists( substr($dst,0,$i), $callrate_cache[$file] ) ) {
    			$call_rate = 0;
    			if ( $callrate_cache[$file][substr($dst,0,$i)][2] == 's' ) {
    				// per second
    				$call_rate = $duration * ($callrate_cache[$file][substr($dst,0,$i)][0] / 60);
    			} elseif ( $callrate_cache[$file][substr($dst,0,$i)][2] == 'c' ) {
    				// per call
    				$call_rate = $callrate_cache[$file][substr($dst,0,$i)][0];
    			} elseif ( $callrate_cache[$file][substr($dst,0,$i)][2] == '1m+s' ) {
    				// 1 minute + per second
    				if ( $duration < 60) {
    					$call_rate = $callrate_cache[$file][substr($dst,0,$i)][0];
    				} else {
    					$call_rate = $callrate_cache[$file][substr($dst,0,$i)][0] + ( ($duration-60) * ($callrate_cache[$file][substr($dst,0,$i)][0] / 60) );
    				}
    			} else {
    				//( $callrate_cache[substr($dst,0,$i)][2] == 'm' ) {
    				// per minute
    				$call_rate = intval($duration/60);
    				if ( $duration%60 > 0 ) {
    					$call_rate++;
    				}
    				$call_rate = $call_rate*$callrate_cache[$file][substr($dst,0,$i)][0];
    			}
    			return array(substr($dst,0,$i),$callrate_cache[$file][substr($dst,0,$i)][0],$callrate_cache[$file][substr($dst,0,$i)][1],$callrate_cache[$file][substr($dst,0,$i)][2],$call_rate);
    		}
    	}
    
    	return array (0,0,'unknown','unknown',0);
    }
    
    ?>
    



    4)
    Дополняем файл стилей /var/www/asterisk-cdr-viewer/style/screen.css

    /* HTML tag styles */
    a {
    	cursor : pointer;
    }
    
    
    abbr[title] {
    	border-style : none none dashed;
    	border-width : medium medium 1px;
    	border-bottom-color : #000000;
    	cursor : help;
    	 white-space : nowrap;
    }
    
    
    body {
    	background-color : #fff;
    	/* Fix for M$ IE < 7 CSS hover */ behavior : url('style/csshover.htc');
    	color : #000;
    	font : 65% Verdana, Arial, Helvetica, sans-serif;
    	margin : 0;
    	padding : 0;
    }
    
    
    img {
    	border-width : 0;
    }
    
    
    /* ID styles */
    #header {
    	background-image : url('../templates/images/header_gradient.png');
    	background-repeat : repeat-x;
    	margin : 0;
    	padding-left : 5%;
    	padding-right : 10%;
    	position : fixed;
    	width : 100%;
    	z-index : 50;
    }
    
    #header_logo {
    	height: 105px;
    	width: 121px;
    	vertical-align: top;
    }
    
    #header_title {
    	color: #000000;
    	font-family: serif;
    	font-size: 32pt;
    	font-variant: small-caps;
    	font-weight: bold;
    	height: 60px;
    }
    
    #header_subtitle {
    	color: #68878a;
    	font-family: Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif;
    	font-size: 12pt;
    	font-weight: bold;
    	height: 60px;
    	padding-left: 10px;
    	vertical-align: top;
    }
    
    #main {
    	margin : 0;
    	padding-top : 115px;
    }
    
    #footer {
    	padding : 5px;
    	border-width : 0;
    	text-align : center;
    }
    
    
    /* Class styles */
    .bar_calls {
    	background-color : #aaf5d0;
    	float : none;
    	padding : 0 0 0 2px;
    }
    
    
    .bar_duration {
    	background-color : #e5edf9;
    	float : none;
    	padding : 0 0 0 2px;
    }
    
    
    .cdr {
    	margin : 0 2%;
    	border-width : 0;
    	white-space : nowrap;
    	width : 96%;
    }
    
    
    .cdr th {
    	background-color : #5ebeff;
    	border-color : #000;
    	border-width : 2px;
    	text-align : center;
    }
    
    
    .cdr .center_col {
    	width : 78%;
    	padding : 2px;
    }
    
    
    .cdr .end_col {
    	width : 11%;
    	padding : 1px;
    }
    
    .cdr .chart_data {
    	padding : 0px;
    	text-align : right;
    }
    
    
    .cdr .img_col {
    	width : 16px;
    	height : 16px;
    }
    
    
    .form legend, .title, .title a {
    	color: #777;
    	font-size: 2em;
    	font-weight: bold;
    }
    
    
    .record {
    	background-color : #fff;
    	empty-cells : hide;
    }
    
    
    .record:hover {
    	background : #ffdca8;
    	color : #000;
    	empty-cells : hide;
    }
    
    
    .record_col {
    	padding-left : 2px;
    	padding-right : 2px;
    	border-width : 0;
    }
    
    
    .center {
    	text-align : center;
    }
    
    .right {
    	padding-right : 80px;
    	text-align : right;
    	font-size: 9pt;
    }
    
    .record_ctrl {
    	position:relative
    }
    
    .record_preview_icon {
    	width:16px;
    	height:16px;
    	cursor:pointer;
    	margin-left:3px;
    }
    
    .record_preview_lt {
    	position:absolute;
    	top:0;
    	left:38px;
    	z-index:99;
    }
    



    5)
    Дополняем файл /var/www/asterisk-cdr-viewer/download.php (обработка range-запросов для аудио-файлов)


    <?php
    
    require_once 'include/config.inc.php';
    
    header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
    header('Pragma: no-cache');
    
    function partial_download($range_header, $request_file, $mime) {
    	$file_size = filesize($request_file);
    	list($b, $range) = explode('=', $range_header);
    	list($first_range) = explode(',', $range);
    	list($part_start,$part_end) = explode('-', $first_range);
    	$start = intval($part_start);
    	if ($part_end)
    		$end = intval($part_end);
    	else
    		$end = $file_size - 1;
    	$chunksize = ($end-$start)+1;
    
    	header('HTTP/1.1 206 Partial Content');
    	header("Content-Type: $mime");
            header('Content-Transfer-Encoding: binary');
    	header("Content-Range: bytes $start-$end/$file_size");
            header('Content-Length: '.$chunksize);
    #       header("Content-Disposition: attachment; filename=\"$_REQUEST[audio]\"");
    
    	$fp = fopen($request_file, 'r');
    	fseek($fp, $start, SEEK_SET);
    	echo fread($fp, $chunksize);
    	fclose($fp);
    }
    
    if (isset($_REQUEST['audio'])) {
    	$extension = strtolower(substr(strrchr($_REQUEST['audio'],"."),1));
    	$ctype ='';
    	switch( $extension ) {
    		case "wav16":
    			$ctype="audio/x-wav";
    			break;
    		case "wav":
    			$ctype="audio/x-wav";
    			break;
    		case "ulaw":
    			$ctype="audio/basic";
    			break;
    		case "alaw":
    			$ctype="audio/x-alaw-basic";
    			break;
    		case "sln":
    			$ctype="audio/x-wav";
    			break;
    		case "gsm":
    			$ctype="audio/x-gsm";
    			break;
    		case "g729":
    			$ctype="audio/x-g729";
    			break;
    		default: 
    			$ctype="application/$system_audio_format";
    			break ;
    	}
    	
    	header("Accept-Ranges: bytes");
    
    	if (!isset($_SERVER['HTTP_RANGE'])) {
    		header("Content-Type: $ctype");
    		header('Content-Transfer-Encoding: binary');
    		header('Content-Length: '.filesize("$system_monitor_dir/$_REQUEST[audio]"));
    		header("Content-Disposition: attachment; filename=\"$_REQUEST[audio]\"");
    		readfile("$system_monitor_dir/$_REQUEST[audio]");
    	}
    	else {
    		partial_download($_SERVER['HTTP_RANGE'], "$system_monitor_dir/$_REQUEST[audio]", $ctype);
    	}
    } elseif (isset($_REQUEST['fax'])) {
    	header('Content-Type: image/tiff');
    	header('Content-Transfer-Encoding: binary');
    	header('Content-Length: '.filesize("$system_fax_archive_dir/$_REQUEST[fax]"));
    	header("Content-Disposition: attachment; filename=\"$_REQUEST[fax]\"");
    	readfile("$system_fax_archive_dir/$_REQUEST[fax]");
    } elseif (isset($_REQUEST['csv'])) {
    	header('Content-Type: text/csv');
    	header('Content-Transfer-Encoding: binary');
    	header('Content-Length: '.filesize("/tmp/$_REQUEST[csv]"));
    	header("Content-Disposition: attachment; filename=\"$_REQUEST[csv]\"");
    	readfile("$system_tmp_dir/$_REQUEST[csv]");
    } elseif (isset($_REQUEST['arch'])) {
    	header('Content-Type: application/x-download');
    	header('Content-Transfer-Encoding: binary');
    	header('Content-Length: '.filesize("$system_monitor_dir/$_REQUEST[arch]"));
    	header("Content-Disposition: attachment; filename=\"$_REQUEST[arch]\"");
    	readfile("$system_monitor_dir/$_REQUEST[arch]");
    }
    
    exit();
    ?>
    




    В результате получаем возможность прослушивания разговоров в веб-интерфейсе (мотать по бегунку прокрутки можно)



    Надеюсь кому-нибудь пригодится:)

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 4

      0
      Все это конечно прикольно, но в CDR Reports итак можно прослушивать записи, скачивать их тоже. Все работает из коробки.
        0
        Безусловно. Но FreePBX был использован по причинам описанным во втором абзаце. К голому Asterisk же вы не прикрутите CDR Reports, к тому же я не хочу давать доступ всем в интерфейс прослушивания, для чего и сделал доступ по логину/паролю.
          0
          В FreePBX же вроде как тот же *-cdr-viewer только чуть образаный с одной стороны и допиленный с другой… В голом asterisk-cdr-viewer вход по логину паролю тоже как практически из коробки ( раскоментировать конфиг не в счет ) + он может использоваться чтоб разграничить что кому можно смотреть. Плеера там нет потому как у автора стоит плагин к броузеру чтоб файлы проигрывать. Кнопка скачать была там всегда. На самом деле с html5 плеером засада:
          — wav который более менее компактный ( с gsm или ulaw/alaw кодированием внутри ) не всеми броузерами поддерживается
          — wav pcm который — места на диске жрет много
          — mp3 — надо во первых кодировать а во вторых тоже не все броузеры играют.
            0
            С этой стороны согласен, разграничение — вещь сугубо деликатная, и не всегда функционал соответствует «хотелкам». Меня просто заморозила фраза «В результате получаем возможность прослушивания разговоров в веб-интерфейсе (мотать по бегунку прокрутки можно)» — что в принципе является изобретением велосипеда. По сути ограничить паролями я могу с помощью .htaccess — и нет необходимости городить огород из дополнительной базы.

            В общем ключевой функционал не оправдан методами достижения цели, все работает(я был приятно удивлен, пытаясь ставить этот вьювер, что в принципе все уже итак работает, достаточно лишь разграничить доступ при необходимости(в нашей компании в ДО по заявке выдается запись того или иного разговора, живой человек не мониторит записи)).

            Делаю вывод, что для меня как для новичка — статья это подсказка, но по сути повторяет базовый функционал FreePBX Distro, который был взят за базу.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое