IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PHP & Base de données Discussion :

Streamer une video cryptée


Sujet :

PHP & Base de données

  1. #1
    Membre régulier
    Homme Profil pro
    Développeur du dimanche
    Inscrit en
    Février 2013
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur du dimanche

    Informations forums :
    Inscription : Février 2013
    Messages : 154
    Points : 105
    Points
    105
    Par défaut Streamer une video cryptée
    Bonjour

    Voici mon projet : j’ai quelques films, entre 200Mo et 1Go, que j’aimerais stocker sur un cloud. Comme la plupart des services de cloud utilisent des bots pour scanner les contenus, et que la vie privée est importante, j’ai opté pour la solution suivante : crypter les .mp4 avant de les envoyer sur le cloud, les charger sur le cloud, et depuis l’interface que je développe, lire les .mp4 décryptés.

    J’ai fait une tentative du côté de Shaka player, qui avait l’air prometteur, mais je me suis trouvé confronté à des problèmes de compatibilité browser, option écartée donc.

    Nouvelle option : php / libsodium.

    Comme mes fichiers sont lourds, file_get_content sature la mémoire de php, je dois donc faire cela par chunks.

    Voici donc le code pour crypter mon fichier :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    function encrypt($msg) 
    {
        $key = "blablabla";
        $nonce = "blablable";
        return sodium_crypto_secretbox($msg, $nonce, $key);
    }
     
    $chunkSize = 1024;
    $src = fopen('vid.mp4', 'rb');
    $dst = fopen('file', 'wb');
    while (!feof($src)) {
        $str = fread($src, $chunkSize); // on lit 1024 octets    
        $str = encrypt($str); // on les crypte : +16 octets soit 1040 octets
        fwrite($dst, $str); // on les écrit dans file
    }
    fclose($src);
    fclose($dst);
    Puis je lis le fichier en stream :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
     
     
    from url : https://github.com/micilini/video-stream/blob/master/src/Providers/VideoStreamProvider.php
     
    class VideoStream // fol
    {
        private $path = "";
        private $stream = "";
        private $start  = -1;
        private $end    = -1;
        private $size   = 0;
     
        function __construct($filePath) 
        {
            $this->path = $filePath;
        }
     
        private function decrypt($msg) {
     
            $key = "blablabla";
            $nonce = "blablabla";
            return sodium_crypto_secretbox_open($msg, $nonce, $key); // decr
        }
     
        /**
         * Open stream
         */
        private function open()
        {
            if (!($this->stream = fopen($this->path, 'rb'))) {
                die('Could not open stream for reading');
            }
        }
     
        /**
         * Set proper header to serve the video content
         */
        private function setHeader()
        {
            ob_get_clean();
            header("Content-Type: video/mp4");
            header("Cache-Control: max-age=2592000, public");
            header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
            header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
            $this->start = 0;
            $this->size  = filesize($this->path);
            $this->end   = $this->size - 1;
            header("Accept-Ranges: 0-".$this->end);
     
            if (isset($_SERVER['HTTP_RANGE'])) {
     
                $c_start = $this->start;
                $c_end = $this->end;
     
                list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                if (strpos($range, ',') !== false) {
                    header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                if ($range == '-') {
                    $c_start = $this->size - substr($range, 1);
                }else{
                    $range = explode('-', $range);
                    $c_start = $range[0];
     
                    $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
                }
                $c_end = ($c_end > $this->end) ? $this->end : $c_end;
                if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
                    header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                $this->start = $c_start;
                $this->end = $c_end;
                $length = $this->end - $this->start + 1;
                fseek($this->stream, $this->start);
                header('HTTPS/1.1 206 Partial Content');
                header("Content-Length: ".$length);
                header("Content-Range: bytes $this->start-$this->end/".$this->size);
                $this->log("Content-Range: bytes $this->start-$this->end/".$this->size);
            }
            else
            {
                header("Content-Length: ".$this->size);
            }  
        }
     
        /**
         * close curretly opened stream
         */
        private function end()
        {
            fclose($this->stream);
            exit;
        }
     
        /**
         * perform the streaming of calculated range
         */
        private function stream()
        {
            $i = $this->start;
     
            $i = 0;
     
            set_time_limit(0);
     
            while(!feof($this->stream) && $i <= $this->end) {
     
                // je lis 1040 octets...
                $bytesToRead = 1040;
     
                if(($i+$bytesToRead) > $this->end) 
                    $bytesToRead = $this->end - $i + 1;
     
                $data = fread($this->stream, $bytesToRead);
                // ... pour en écrire 1024 en clear 
                $data = $this->decrypt($data);
     
                echo $data;
                flush();
                $i += $bytesToRead;
            }
        }
     
        /**
         * Start streaming video content
         */
        function start()
        {
            $this->open();
            $this->setHeader();
            $this->stream();
            $this->end();
        }
    }
     
    $filePath = "file";
    $stream = new VideoStream($filePath); 
    $stream->start();
    Problème… Je peux parfaitement lire le stream sur Firefox mais pas sur Brave/Chrome ou Safari A noter que si je stream le .mp4 directement (la version clear, donc pas de décryptage au milieu), aucun problème sur ces deux autres navigateurs….

    Dans les deux cas, je reçois une erreur 500, internal serveur error.

    A noter également que j’avais fait des test en convertissant en base64 avant de crypter (oui, c’est mal), et donc en décodant le base64 dans le streamer. Si je supprime tout le bloc du "if (isset($_SERVER['HTTP_RANGE'])) {", je ne peux pas avancer dans le player mais au moins, ça lit…

    J’en déduis que mes problèmes viennent des réponses HTTP mais ici j’arrive en limite de connaissance…. Je me souviens juste avoir fait des tests pour lire la valeur de $_SERVER['HTTP_RANGE’] depuis php, et avait été surpris de constater une valeur du genre "byte=546566-", qui si je comprends bien, indique que le stream ne débute pas au tout début de la vidéo. Et ce alors que je ne cliquais nulle part, c’était juste au chargement de la page.

    Voilà… Pas évident, et c’est aussi peut être encore des problèmes de browser, mais si ça fonctionne en clear ça devrait aussi fonctionner en crypté non ? Peut être que le fait d’avoir crypté modifie les size, rendant tout le bloc "if (isset($_SERVER['HTTP_RANGE'])) {" faux ?

    Merci pour vos réponses
    "There should be no boundaries to human endeavor" - Hawking
    Retrouvez moi sur GitHub : https://github.com/JeanLouisMassei

  2. #2
    Expert éminent
    Avatar de Séb.
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    5 101
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mars 2005
    Messages : 5 101
    Points : 8 211
    Points
    8 211
    Billets dans le blog
    17
    Par défaut
    Je peux parfaitement lire le stream sur Firefox mais pas sur Brave/Chrome ou Safari
    Peut-être que Firefox droppe ~silencieusement les trames vidéos invalides et possède un mécanisme de synchronisation, et pas Chrome.
    => Logue les retours de sodium_crypto_secretbox_open()

    Dans les deux cas, je reçois une erreur 500, internal serveur error.
    Que donne le log d'erreurs du serveur web / de PHP ?

    si ça fonctionne en clear ça devrait aussi fonctionner en crypté non ?
    Tu cryptes des blocs de 1024 bits et obtiens des blocs de 1040 bits
    Est-ce que tu prends en compte le fait que ce sont exactement ces mêmes blocs de 1040 bits qui doivent être décryptés ? Sinon les données obtenues seront invalides.
    Un problème exposé clairement est déjà à moitié résolu
    Keep It Smart and Simple

  3. #3
    Membre régulier
    Homme Profil pro
    Développeur du dimanche
    Inscrit en
    Février 2013
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur du dimanche

    Informations forums :
    Inscription : Février 2013
    Messages : 154
    Points : 105
    Points
    105
    Par défaut
    Incroyable Ligne 82 du streamer j'avais ce $this->log qui datait d'un debug d'il y a quelques jours. Je ne sais pas si c'est ça (ça me surprend, quand même, que firefox ait pu lancer le machin avec cette fonction qui n'existe pas), mais depuis que j'ai remis cette fonction, brave/chrome a le même comportement que firefox.

    Peut-être que Firefox droppe ~silencieusement les trames vidéos invalides et possède un mécanisme de synchronisation, et pas Chrome. Logue les retours de sodium_crypto_secretbox_open()
    => en faisant $res = $this->stream = fopen($this->path, 'rb'); $this->log($res);
    => j'obtiens des messages futiles, logs.txt qui existe pas et fopen qui attend d'écrire une string mais reçoit un bool à écrire
    => en gros tout va bien je pense

    Que donne le log d'erreurs du serveur web / de PHP ?
    => voir au dessus

    Tu cryptes des blocs de 1024 bits et obtiens des blocs de 1040 bits
    Est-ce que tu prends en compte le fait que ce sont exactement ces mêmes blocs de 1040 bits qui doivent être décryptés ? Sinon les données obtenues seront invalides.
    => je pense que oui, je veux dire, à l'encryptage j'ai commencé à l'octet 0 et je suis allé de 1024 en 1024, pour écrire de 1040 en 1040. Si tout va bien, sur le streamer, je commence à 0 aussi dans la fonction stream, et je déroule de 1040 en 1040 pour écrire de 1024 en 1024. Ai-je répondu à la question ?

    Restent deux questions :

    1° Safari n'arrive toujours pas à lire le stream, rien ne remonte dans les logs du serveur. Erreur : "Failed to load resource: Charge gérée par module". Tu as une idée ?
    2° Sur Firefox, on peut avancer le lecteur jusqu'aux partie qui sont chargées, mais on ne peut déplacer le curseur à un endroit où la vidéo n'est pas encore chargée. Pourtant je pensais que tout le gros if du streamer et notament le header partial content servait justement à gérer ça ? Sur brave/chrome, impossible de bouger le curseur.
    "There should be no boundaries to human endeavor" - Hawking
    Retrouvez moi sur GitHub : https://github.com/JeanLouisMassei

  4. #4
    Expert éminent
    Avatar de Séb.
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    5 101
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mars 2005
    Messages : 5 101
    Points : 8 211
    Points
    8 211
    Billets dans le blog
    17
    Par défaut
    => j'obtiens des messages futiles, logs.txt qui existe pas et fopen qui attend d'écrire une string mais reçoit un bool à écrire
    => en gros tout va bien je pense
    Comment ça ? Il faut loguer l'activité et chercher les erreurs.

    Tu cryptes des blocs de 1024 bits et obtiens des blocs de 1040 bits
    Est-ce que tu prends en compte le fait que ce sont exactement ces mêmes blocs de 1040 bits qui doivent être décryptés ? Sinon les données obtenues seront invalides.
    => je pense que oui, je veux dire, à l'encryptage j'ai commencé à l'octet 0 et je suis allé de 1024 en 1024, pour écrire de 1040 en 1040. Si tout va bien, sur le streamer, je commence à 0 aussi dans la fonction stream, et je déroule de 1040 en 1040 pour écrire de 1024 en 1024. Ai-je répondu à la question ?
    Ce qui m'inquiète c'est surtout à la lecture. Ça se passe comment ?
    Si tu reçois une requête avec un Range: bytes 1030-3000, tu lui envoies quoi exactement ?
    Un problème exposé clairement est déjà à moitié résolu
    Keep It Smart and Simple

  5. #5
    Membre régulier
    Homme Profil pro
    Développeur du dimanche
    Inscrit en
    Février 2013
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur du dimanche

    Informations forums :
    Inscription : Février 2013
    Messages : 154
    Points : 105
    Points
    105
    Par défaut
    Salut,

    En fait je suis sur Wamp, comme je gère ça en localhost pour l'instant. Je peux consulter les logs de Wamp, qui n'affiche pas tout ce qu'afficherait un serveur apache/php je suppose. L'erreurs que log.txt n'exsite pas, c'est quand je fais $this->log, j'ouvre un fichier log.txt avec fopen pour m'en servir de console, donc erreur minime.

    Pour le range 1030 3000 c'est une excellente question ! J'y ai déjà pensé, je me suis dit que ça mettrait la pagaille dans mes chunkcs, j'avais donc fait une fonction pour rapprocher start de la valeur supérieur la plus proche d'un multiple de 1040, ci dessous ligne 22. Ça n'a aucun effet (mon objectif maintenant est d'arrover à changer le curseur de position et à forcer la page à restreamer depuis la nouvelle position de mon curseur. Dois-je faire la même chose pour end ? Ne penses tu pas que size soit aussi affecté par le fait que mes 1024 prennent 1040, rendant la valeur de size erronée ? Je t'avoue que patauge, les headers c'est quelque chose que je découvre complètement avec ce projet.

    Je m'interroge surtout pourquoi je reçois des requêtes le range ne commence par par 0, en fait.. Quand je charge le page de mon streamer je dois commencer à 0 normalement non ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
     
     
    private function setHeader()
        {
            ob_get_clean();
            header("Content-Type: video/mp4");
            header("Cache-Control: max-age=2592000, public");
            header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
            header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
     
            $this->start = 0;
            $this->size  = filesize($this->path);
            $this->end   = $this->size - 1;
            header("Accept-Ranges: 0-".$this->end);
     
     
            if (isset($_SERVER['HTTP_RANGE'])) {
     
                $c_start = $this->start;
                $c_end = $this->end;
     
                $c_start /= 1040;
                $c_start = round($c_start);
                $c_start *= 1040;
     
                list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                if (strpos($range, ',') !== false) {
                    header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                if ($range == '-') {
                    $c_start = $this->size - substr($range, 1);
                }else{
                    $range = explode('-', $range);
                    $c_start = $range[0];
     
                    $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
                }
                $c_end = ($c_end > $this->end) ? $this->end : $c_end;
                if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
                    header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                $this->start = $c_start;
                $this->end = $c_end;
                $length = $this->end - $this->start + 1;
                fseek($this->stream, $this->start);
                header('HTTPS/1.1 206 Partial Content');
                header("Content-Length: ".$length);
                header("Content-Range: bytes $this->start-$this->end/".$this->size);
                $this->log('lol');
            }
            else
            {
                header("Content-Length: ".$this->size);
            }  
     
        }
    "There should be no boundaries to human endeavor" - Hawking
    Retrouvez moi sur GitHub : https://github.com/JeanLouisMassei

  6. #6
    Expert éminent
    Avatar de Séb.
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    5 101
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations professionnelles :
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mars 2005
    Messages : 5 101
    Points : 8 211
    Points
    8 211
    Billets dans le blog
    17
    Par défaut
    https://developer.mozilla.org/en-US/...Range_requests

    Ici ils indiquent que la réponse HTTP initiale doit être de la forme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    HTTP/1.1 200 OK
    …
    Accept-Ranges: bytes
    Content-Length: 146515
    Alors que ton script donne une plage d'octets sur Accept-Ranges

    À mon avis il faut donner un Content-Length égal à la taille avant cryptage
    et décrypter / ajuster les réponses au plus près des morceaux

    Sinon, si tu déclares une taille de 1040 (exemple extrême) tu ne pourras jamais satisfaire une requête sur une Range: 1024- alors que la demande est légitime

    Si tu ne t'en sors pas partage une petite vidéo de quelques secondes sur laquelle on pourrait faire des tests communs



    À propos de l'interrogation d'une 1re Range ne commençant pas à 0
    Ça ne m'inquiète pas forcément, entre les caches et les conteneurs dont les métas ne sont pas forcément en début de fichier...
    (sur les .avi les index étaient en fin de fichier)
    Un problème exposé clairement est déjà à moitié résolu
    Keep It Smart and Simple

  7. #7
    Membre régulier
    Homme Profil pro
    Développeur du dimanche
    Inscrit en
    Février 2013
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur du dimanche

    Informations forums :
    Inscription : Février 2013
    Messages : 154
    Points : 105
    Points
    105
    Par défaut
    Salut

    Donc j'ai lu pas mal de choses sur les headers...

    Voici un lien vers la vidéo que j'utilise pour mes tests :
    https://drive.google.com/file/d/1pW9...ew?usp=sharing
    Elle est plus longe que quelques secondes mais j'ai essayé mes premiers codes (qui, au moins, lisaient la vidéo) avec une vidéo plus courte, ça ne fonctionnait pas, j'en ai déduit que le problème venait de la video. Dans ce lien ci-dessus donc, c'est la vidéo cryptée que tu trouveras.

    Et voici un lien vers la dernière version de mon streamer :
    -> lit les packets 1040 par 1040 et les décrypte en 1024 par 1024
    -> fonction clearStart ajoutée pour que start commence toujours au début d'un chunck
    -> fonction clearSize ajoutée pour que size soit celle de la vidéo en clear et pas celle de la vidéo cryptée
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
     
     
    <?php
     
    class VideoStream // fol
    {
        private $path = "";
        private $stream = "";
        private $start  = -1;
        private $end    = -1;
        private $size   = 0;
     
        function __construct($filePath) 
        {
            $this->path = $filePath;
        }
     
        private function decrypt($msg) {
            $key = "v6jM4s0hYqa52m8Bt5c1z7Celv6hFbnF";
            $nonce = "mf5cOb6g19QRbX93bD6Fpj5d";
            return sodium_crypto_secretbox_open($msg, $nonce, $key); // decr
        }
     
        /**
         * Open stream
         */
        private function open()
        {
            if (!($this->stream = fopen($this->path, 'rb'))) {
                die('Could not open stream for reading');
            }  
        }
     
        // renvoie size avant cryptage
        private function clearSize($val)
        {
            $fullChuncksNb = floor($val/1040);
            $lastChunckSize = $val - ($fullChuncksNb * 1040) - 16; // 16 car cryptés
            return $fullChuncksNb * 1024 + $lastChunckSize;
        }
     
        private function clearStart($val)
        {
            $val /= 1040;
            $val = floor($val);
            return $val * 1024;
        }
     
        /**
         * Set proper header to serve the video content
         */
        private function setHeader()
        {
            ob_get_clean();
     
            header("Content-Type: video/mp4");
            header("Cache-Control: max-age=2592000, public");
            header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
            header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
            $this->start = 0;
            $this->size  = $this->clearSize(filesize($this->path));
            $this->end   = $this->size - 1;
     
            header("Accept-Ranges: 0-".$this->end); // works on CH and FF
            //header("Accept-Ranges: bytes"); // works only on FF 
     
            if (isset($_SERVER['HTTP_RANGE'])) {
     
                $c_start = $this->start;
                $c_end = $this->end;
     
                list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                if (strpos($range, ',') !== false) {
                    header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                if ($range == '-') {
                    $c_start = $this->size - substr($range, 1);
                }else{
                    $range = explode('-', $range);
                    $c_start = $range[0];
                    $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
                }
     
                $c_start = $this->clearStart($c_start);
     
                $c_end = ($c_end > $this->end) ? $this->end : $c_end;
                if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
                    header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                $this->start = $c_start;
                $this->end = $c_end;
                $length = $this->end - $this->start + 1;
                fseek($this->stream, $this->start);
                header('HTTPS/1.1 206 Partial Content');
                header("Content-Length: ".$length);
                header("Content-Range: bytes $this->start-$this->end/".$this->size);
            }
            else
            {
                header("Content-Length: ".$this->size);
            }  
        }
     
        /**
         * close curretly opened stream
         */
        private function end()
        {
            fclose($this->stream);
            exit;
        }
     
        /**
         * perform the streaming of calculated range
         */
        private function stream()
        {
            $i = $this->start;
     
            $i = 0;
     
            set_time_limit(0);
     
            while(!feof($this->stream) && $i <= $this->end) {
     
                // 1024 caractères cryptés en donnent 1040 (tj 16 de plus)
                // en cryptant, j'ai pris des paquets de 1024 chars base64 et pr chaque chnk de 1024, je crypte -> écrit 1040
                $bytesToRead = 1040;
     
                if(($i+$bytesToRead) > $this->end) 
                    $bytesToRead = $this->end - $i + 1;
     
                $data = fread($this->stream, $bytesToRead);
                $data = $this->decrypt($data);
     
                echo $data;
                flush();
                $i += $bytesToRead;
            }
        }
     
        /**
         * Start streaming video content
         */
        function start()
        {
            $this->open();
            $this->setHeader();
            $this->stream();
            $this->end();
        }
    }
     
    $filePath = "file";
    $stream = new VideoStream($filePath); 
    $stream->start();
     
    ?>
    Rien n'y fait, le lecteur lit bien la vidéo en la décryptant (c'est déjà pas mal), mais impossible d'avancer dans la lecture en cliquant dans le player...

    J'avais regardé les requêtes que mon navigateur envoie et celles que le serveur répond (au chargement de la page, avec tout le fichier, ou en cliquant pour avancer, donc avec un partial content), tout est correct : j'ai fait le même streamer pour lire le .mp4 en clear et tous les headers sont les mêmes avec les mêmes valeurs.

    J'ai un peu du mal à comprendre Encore une fois, je n'écarte pas l'hypothèse d'un problème de brower
    "There should be no boundaries to human endeavor" - Hawking
    Retrouvez moi sur GitHub : https://github.com/JeanLouisMassei

  8. #8
    Membre régulier
    Homme Profil pro
    Développeur du dimanche
    Inscrit en
    Février 2013
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur du dimanche

    Informations forums :
    Inscription : Février 2013
    Messages : 154
    Points : 105
    Points
    105
    Par défaut
    Trouvé.

    encrypt :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
     
    <?php 
     
    function encrypt($msg) 
    {
        $key = "v6jM4s0hYqa52m8Bt5c1z7Celv6hFbnF";
        $nonce = "mf5cOb6g19QRbX93bD6Fpj5d";
        return sodium_crypto_secretbox($msg, $nonce, $key);
    }
     
    $chunkSize = 1024;
    $src = fopen('vid.mp4', 'rb');
    $dst = fopen('file', 'wb');
     
    while (!feof($src)) {
        $str = fread($src, $chunkSize); 
        $str = encrypt($str); 
        fwrite($dst, $str);
    }
     
    fclose($src);
    fclose($dst);
     
    ?>
    decrypt and stream :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
     
     
    <?php
     
    class VideoStream
    {
        private $path = "";
        private $stream = "";
        private $start  = -1;
        private $end    = -1;
        private $size   = 0;
     
        function __construct($filePath) 
        {
            $this->path = $filePath;
        }
     
        /**
         * Decrypts a msg using sodium
         */
        private function decrypt($msg) {
            $key = "v6jM4s0hYqa52m8Bt5c1z7Celv6hFbnF";
            $nonce = "mf5cOb6g19QRbX93bD6Fpj5d";
            return sodium_crypto_secretbox_open($msg, $nonce, $key); // decr
        }
     
        /**
         * Open stream
         */
        private function open()
        {
            if (!($this->stream = fopen($this->path, 'rb'))) {
                die('Could not open stream for reading');
            }  
        }
     
        /**
         * Close curretly opened stream
         */
        private function end()
        {
            fclose($this->stream);
            exit;
        }
     
        /**
         * Calculate the size of the decrypted video, based on $val, 
         * the size of the encrypted video.
         * Works as long as the video has been encrypted by chunks of 1024 bytes
         * (each chunck takes 16 bytes more when encrypted)
         */
        private function clearSize($val)
        {
            $fullChuncksNb = floor($val/1040); // nb of chuncks of 1024 bytes (1024+16)
            $lastChunckSize = $val - ($fullChuncksNb * 1040) - 16; // size of the last chunk (decrypted)
            return $fullChuncksNb * 1024 + $lastChunckSize;
        }
     
        /**
         * Set proper header to serve the video content
         */
        private function setHeader()
        {
            ob_get_clean();
     
            header("Content-Type: video/mp4");
            header("Cache-Control: max-age=2592000, public");
            header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
            header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
            $this->start = 0;
            $this->size  = $this->clearSize(filesize($this->path)); 
            $this->cryptSize = filesize($this->path);
            $this->end   = $this->size - 1;
            header("Accept-Ranges: bytes"); 
     
            if (isset($_SERVER['HTTP_RANGE'])) {
     
                $c_start = $this->start;
                $c_end = $this->end;
     
                list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                if (strpos($range, ',') !== false) {
                    header('HTTP/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                if ($range == '-') {
                    $c_start = $this->size - substr($range, 1);
                }else{
                    $range = explode('-', $range);
                    $c_start = $range[0];
                    $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
                }
                $c_end = ($c_end > $this->end) ? $this->end : $c_end;
                if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
                    header('HTTP/1.1 416 Requested Range Not Satisfiable');
                    header("Content-Range: bytes $this->start-$this->end/$this->size");
                    exit;
                }
                $this->start = $c_start;
                $this->end = $c_end;
                $this->length = $this->end - $this->start + 1;
     
                header('HTTP/1.1 206 Partial Content');
                header("Content-Length: " . $this->length); 
                header("Content-Range: bytes $this->start-$this->end/".$this->size);
            }
            else
            {
                header("Content-Length: ". $this->size);
            }  
        }
     
        /**
         * Perform the streaming of calculated range
         */
        private function stream()
        {
            set_time_limit(0);
     
            $bytesToRead = 1040;
     
            // z_start = index of the chunck containing start
            $z_start = $this->start;        
            $z_start /= 1024;               
            $z_start = floor($z_start);    
     
            // z_end = index of the chunck containing end 
            $z_end = $this->end;            
            $z_end /= 1024;                 
            $z_end = floor($z_end);  
     
            // let's go at the beginning of the chunk containing z_start
            fseek($this->stream, $z_start * $bytesToRead); 
     
            // read the chunk and decrypt it
            $data = fread($this->stream, $bytesToRead); // size 1040 
            $data = $this->decrypt($data);              // size 1024
     
            // extracts the relevant bytes from this chunk
            $len = ($z_end > $z_start) ? 1023 - ($this->start%1024) + 1 : $this->end - $this->start + 1;
            $bytes = mb_substr($data, ($this->start%1024), $len, '8bit');
     
            // keep record of how much bytes has been returned so far 
            $written = $len;
     
            echo $bytes; 
            flush();
     
            // i = pos of the cursor in the crypted stream (could be 1040, 2080, ...)
            $i = ftell($this->stream);
     
            while (!feof($this->stream) && $i < $this->cryptSize)
            {
                // if we are in last chunk of the crypted file (length < 1024)
                if (($i + $bytesToRead) > $this->cryptSize - 1) 
                    $bytesToRead = $this->cryptSize - $i;
     
                // read and decrypt
                $data = fread($this->stream, $bytesToRead);
                $data = $this->decrypt($data);
     
                // if I don't need all the bytes of this chunck
                if ($written + 1024 > $this->length) {
                    $len = $this->length - $written; 
                    $bytes2 = mb_substr($data, 0, $len, '8bit');
                    echo $bytes2;
                    flush(); 
                    return;
                } else {
                    echo $data;
                    $written += 1024;
                    flush(); 
                }
     
                $i += 1040;
            }
        }
     
        /**
         * Start streaming video content
         */
        function start()
        {
            $this->open();
            $this->setHeader();
            $this->stream();
            $this->end();
        }
    }
     
    $filePath = "file";
    $stream = new VideoStream($filePath); 
    $stream->start();
     
    ?>
    Bilan :
    - Accept-Ranges: 0-".$this->end était accepté pour chrome et safari, mais Accept-Ranges: bytes permet en plus à firefox de lire la vid
    - Content length doit être égal à la taille avant cryptage : vrai
    - Décrypter / ajuster les réponses au plus près des morceaux : faux. Si mes chuncks font 1024 et que le browser demande le range 1022-1025, il faut décrypter le premier chunck, extraire les octets 1022 et 1023, les streamer, puis passer au chunck suivant, le décrypter, et extraire les octets 1024 et 1025. Les browsers attendent que le Content-Range renvoyé soit exactement celui qui a été demandé.

    Success !
    "There should be no boundaries to human endeavor" - Hawking
    Retrouvez moi sur GitHub : https://github.com/JeanLouisMassei

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Streamer une video qui est elle meme distante et en streaming
    Par mapmip dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 1
    Dernier message: 20/01/2022, 17h22
  2. caché les proprietes d'une video
    Par legreedo dans le forum Balisage (X)HTML et validation W3C
    Réponses: 5
    Dernier message: 30/08/2005, 14h21
  3. Enregistrer une video rmv (real media)
    Par Harry dans le forum Vidéo
    Réponses: 1
    Dernier message: 19/06/2005, 11h35
  4. Ajouter du texte sur une vidéo
    Par ChIcKeN78 dans le forum Autres Logiciels
    Réponses: 2
    Dernier message: 01/06/2005, 10h43
  5. Créer une video à partir d'images
    Par Akta3d dans le forum DirectX
    Réponses: 5
    Dernier message: 13/07/2004, 14h46

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo