Skip to content

Commit

Permalink
Add PHP files for spycam POC example to be refined
Browse files Browse the repository at this point in the history
  • Loading branch information
sarxos committed Jul 22, 2013
1 parent 0acd38d commit b005bac
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace v1\resource;

use tonic\Resource as Resource;
use tonic\Request as Request;
use tonic\Response as Response;


/**
* Handle tick picture upload for specific user.
*
* @author Bartosz Firyn (SarXos)
*/
class Heartbeat extends Resource {

/**
* Receive streaming hartbeat.
*
* @param RESTRequest request
* @return RESTResponse
*/
function put(Request $request, $uid) {
$response = new Response($request);
$response->code = Response::OK;
$response->body = $uid;
return $response;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<?php

namespace streaming;

use log\KLogger as KLogger;


/**
* MJPEG stream reader.
*
* @author Bartosz Firyn (SarXos)
*/
class MJPEGStreamer {

private static $BOUNDARY = 'mjpeg-frame';

private $log = null;

private $initialized = false;

private $running = true;

private $buffer = array();

private $name = null;

private $sock = null;


public function __construct($name) {
$this->name = $name;
$this->sock = $this->buildSockPath($this->name);
$this->log = new KLogger('files' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . $name . '.log', KLogger::DEBUG);
}

public function shutdown() {
$this->log->info('Removing sock file');
unlink($this->sock);
}

private function buildSockPath($name) {
return getcwd() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'sock' . DIRECTORY_SEPARATOR . $this->name;
}

public function init() {

if (file_exists($this->sock)) {
$this->log->error("Sock file already exists, concurrent process cannot be started");
exit();
}

$this->log->info('Streamer initialization');

// connection keep-alive, in other case browser will close it when receive last frame
header('Connection: keep-alive');

// disable caches
header('Cache-Control: no-cache');
header('Cache-Control: private');
header('Pragma: no-cache');

// x-mixed-replace to stream JPEG images
header('Content-type: multipart/x-mixed-replace; boundary=' . self::$BOUNDARY);

// set unlimited so PHP doesn't timeout during a long stream
set_time_limit(0);

// ignore user abort script
ignore_user_abort(true);

@apache_setenv('no-gzip', 1); // disable apache gzip compression
@ini_set('zlib.output_compression', 0); // disable PHP zlib compression
@ini_set('implicit_flush', 1);

// flush all current buffers
$k = ob_get_level();
for ($i = 0; $i < $k; $i++) {
ob_end_flush();
}

register_shutdown_function(array($this, 'shutdown'));

fclose(fopen($this->sock, 'w'));

$this->initialized = true;
}

private function read() {

$this->log->debug('Read sock file');

clearstatcache();
if (filesize($this->sock) === 0) {
$this->log->debug('Sock file is empty');
return;
}

$f = fopen($this->sock, 'r');
flock($f, LOCK_SH);
$frames = @file($this->sock, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
flock($f, LOCK_UN);
fclose($f);

fclose(fopen($this->sock, 'w'));

if (!empty($frames)) {
$this->buffer = array_merge($this->buffer, $frames);
$this->log->debug('Read ' . count($frames) . ' frames');
}
}

private function control() {

$filen = $this->sock . '.ctrl';

if (!file_exists($filen)) {
$this->log->debug('Control file does not exist');
return;
}

$f = fopen($filen, 'r');
flock($f, LOCK_SH);
$config = @json_decode(file_get_contents($filen), true);
flock($f, LOCK_UN);
fclose($f);

$this->log->debug('Read control file ' . print_r($config, true));

if (!empty($config)) {
if (isset($config['status']) && $config['status'] !== 'running') {
$this->log->debug('Found close directive, set running false');
$this->running = false;
}
} else {
$this->log->fatal('Control configuration is empty!');
$this->log->fatal('Control configuration is empty!');
}
}

public function serve() {

$this->log->info('MJPEG streaming started');

if (!$this->initialized) {
$this->init();
}

while (true) {

if(connection_status() != CONNECTION_NORMAL) {
$this->log->debug('Connection closed');
//break;
}

$this->control();
$this->read();

$this->log->debug('Frames buffer size is ' . count($this->buffer));

if (!empty($this->buffer)) {
$frame = array_shift($this->buffer);
$content = file_get_contents($frame);
print('--' . self::$BOUNDARY . "\nContent-type: image/jpeg\n\n" . $content);
flush();
}

sleep(1);

if (!$this->running) {
$this->log->info('Stopping');
break;
}
}

exit(0);
}
}




Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace v1\resource;

use streaming\MJPEGStreamer;

use tonic\Resource as Resource;
use tonic\Request as Request;
use tonic\Response as Response;
use \PDO as PDO;

/**
* @author Bartosz Firyn (SarXos)
*/
class Stream extends Resource {

/**
* Serve MJPEG stream.
*
* @param RESTRequest request
* @param Request $request
* @param string $uid - user ID
* @param string $camid - camera ID
* @return RESTResponse
*/
function get(Request $request, $uid, $camid) {

if ($this->canAcess($uid, $camid)) {
$streamer = new MJPEGStreamer($uid . '.' . $camid);
$streamer->serve();
} else {
$response = new Response($request);
$response->code = Response::FORBIDDEN;
$response->body = 'You are not authorized to access this resource';
return $response;
}
}

private function canAcess($uid, $camid) {

$uid = mysql_real_escape_string($uid);
$camid = mysql_real_escape_string($camid);

$pdo = new PDO('mysql:host=localhost;dbname=spycam', 'root', 'secret');

$sth = $pdo->prepare('select * from users where uid = :uid');
$sth->execute(array(':uid' => $uid));

$user = $sth->fetch();

$sth = $pdo->prepare('select * from cams where uid = :uid and camid = :camid');
$sth->execute(array(':uid' => $uid, ':camid' => $camid));

$camera = $sth->fetch();

// TODO validate passwd

if ($user !== false && $camera !== false) {
return true;
} else {
return false;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

# Used to separate multipart
$boundary = "spycam-mjpeg-stream";

# We start with the standard headers. PHP allows us this much
header("Cache-Control: no-cache");
header("Cache-Control: private");
header("Pragma: no-cache");
header("Content-type: multipart/x-mixed-replace; boundary=$boundary");

# From here out, we no longer expect to be able to use the header() function
print "--$boundary\n";

# set unlimited so PHP doesn't timeout during a long stream
set_time_limit(0);

@apache_setenv('no-gzip', 1); // disable apache gzip compression
@ini_set('zlib.output_compression', 0); // disable PHP zlib compression
@ini_set('implicit_flush', 1); // set implicit flush

// and flush all current buffers

for ($i = 0; $i < ob_get_level(); $i++) {
ob_end_flush();
}

ob_implicit_flush(1);


// streaming

$files = scandir('uploads');
$i = 0;

# The loop, producing one jpeg frame per iteration
while (true) {

if ($i >= count($files)) {
break;
}

$file = $files[$i++];
if (substr($file, strlen($file) - 4, 4) !== '.jpg') {
continue;
}

# Per-image header, note the two new-lines
print("Content-type: image/jpeg\n\n");
//print("Content-type: text/plain\n\n");

# Your function to get one jpeg image
print(file_get_contents("uploads/$file"));
//print("uploads/$file");

//flush();
//ob_flush();
usleep(200000);

# The separator
print("--$boundary\n");

}

0 comments on commit b005bac

Please sign in to comment.