Szukaj…


Uwagi

W tym temacie omówiono różne typy mediów i sposoby ich użycia z płótnem w interfejsie 2D.

Typy mediów mają kategorie ogólne i specyficzne dla formatu

Rodzaje mediów

  • Animacje
  • Wideo
  • Zdjęcia
  • Obrazy HD
  • Grafika wektorowa
  • Animowane obrazy

Formaty mediów

  • Jpg / Jpeg
  • Png
  • Gif
  • SVG
  • M-JPEG
  • Webm
  • Webp

Zdjęcia

Istnieje wiele różnych formatów obrazów obsługiwanych przez przeglądarki, ale żadna przeglądarka nie obsługuje ich wszystkich. Jeśli masz określone formaty obrazów, chcesz korzystać z Przeglądarek Wiki, a obsługiwane formaty obrazów zapewniają dobry przegląd.

Najlepsze wsparcie jest dla 3 głównych formatów, „jpeg”, „png” i „gif” ze wszystkimi głównymi przeglądarkami zapewniającymi wsparcie.

JPEG

Obrazy JPEG najlepiej nadają się do zdjęć i obrazów podobnych do zdjęć. Nie pożyczają sobie dobrze wykresów, diagramów i tekstu. Obrazy JPEG nie obsługują przezroczystości.

Canvas może wyprowadzać obrazy JPEG przez canvas.toDataURL i canvas.toBlob i zapewnia ustawienie jakości. Ponieważ JPEG nie obsługuje przezroczystości, wszystkie przezroczyste piksele zostaną zmieszane z czernią dla ostatecznego wyjścia JPG. Powstały obraz nie będzie idealną kopią płótna.

JPEG na wikipedii

PNG

Obraz PNG to obrazy najwyższej jakości i mogą również zawierać kanał alfa dla przezroczystych pikseli. Dane obrazu są kompresowane, ale nie tworzą artefaktów, takich jak obrazy JPG.

Ze względu na bezstratną kompresję i obsługę kanału alfa pliki PNG są używane do gier, obrazów elementów interfejsu użytkownika, wykresów, diagramów, tekstu. Gdy używasz ich do zdjęć i obrazów podobnych do zdjęć, ich rozmiar pliku może być znacznie większy niż JPEG. .

Format PNG zapewnia również obsługę animacji, chociaż obsługa przeglądarki jest ograniczona, a dostęp do animacji do użytku na kanwie można uzyskać tylko za pośrednictwem interfejsów API i bibliotek JavaScript

Canvas może być używany do kodowania obrazów PNG przez canvas.toDataURL i canvas.toBlob chociaż format wyjściowy jest ograniczony do skompresowanego 32-bitowego RGBA. PNG zapewni idealną pikselową kopię płótna.

PNG na wikipedii

GIF

Pliki GIF są używane do krótkich animacji, ale można ich także używać do tworzenia wysokiej jakości wykresów, diagramów i obrazów podobnych do tekstu. Pliki GIF mają bardzo ograniczoną obsługę kolorów, maksymalnie 256 kolorów na ramkę. Dzięki przetwarzaniu obrazu tasakiem obrazy gif mogą dawać zaskakująco dobre wyniki, szczególnie gdy są animowane. Gify zapewniają również przezroczystość, chociaż jest to ograniczone do włączania lub wyłączania

Podobnie jak w przypadku PNG, animacje GIF nie są bezpośrednio dostępne do użycia na płótnie i będziesz potrzebować JavaScript API lub biblioteki, aby uzyskać dostęp. GIF nie może być zapisany przez płótno i wymaga do tego API lub biblioteki.

GIF na wikipedii

Ładowanie i wyświetlanie obrazu

Aby załadować obraz i umieścić go na płótnie

var image = new Image();  // see note on creating an image
image.src = "imageURL";
image.onload = function(){
    ctx.drawImage(this,0,0);
}

Tworzenie obrazu

Istnieje kilka sposobów tworzenia obrazu

  • new Image()
  • document.createElement("img")
  • <img src = 'imageUrl' id='myImage'> Jako część treści HTML i odzyskane za pomocą document.getElementById('myImage')

Obraz jest HTMLImageElement

Właściwość Image.src

Obraz src może być dowolnym poprawnym adresem URL obrazu lub zakodowanym adresem danychURL. Aby uzyskać więcej informacji na temat formatów obrazów i obsługi, zobacz Uwagi do tego tematu.

  • image.src = "http://my.domain.com/images/myImage.jpg"
  • image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=" *

* DataURL to obraz gif 1 na 1 piksel zawierający czerń

Uwagi na temat ładowania i błędów

Ładowanie obrazu rozpocznie się po ustawieniu jego właściwości src. Ładowanie jest zsynchronizowane, ale zdarzenie onload nie zostanie wywołane, dopóki funkcja lub kod nie wyjdzie / nie zwróci.

Jeśli otrzymasz obraz ze strony (na przykład document.getElementById("myImage") ) i ustawiono src może on zostać załadowany lub nie. Możesz sprawdzić status obrazu za pomocą HTMLImageElement.complete co będzie true jeśli zostanie ukończone. Nie oznacza to, że obraz został załadowany, to znaczy, że ma albo

  • załadowany
  • Wystąpił błąd
  • Właściwość src nie została ustawiona i jest równa pustemu ciągowi ""

Jeśli obraz pochodzi z niewiarygodnego źródła i może nie być dostępny z różnych powodów, wygeneruje zdarzenie błędu. Kiedy to nastąpi, obraz będzie w stanie zepsutym. Jeśli następnie spróbujesz narysować go na płótnie, pojawi się następujący błąd

Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.

image.onerror = myImgErrorHandler zdarzenie image.onerror = myImgErrorHandler , możesz podjąć odpowiednie działania, aby zapobiec błędom.

Rysowanie obrazu SVG

Aby narysować wektorowy obraz SVG, operacja nie różni się od obrazu rastrowego:
Najpierw musisz załadować obraz SVG do elementu HTMLImage, a następnie użyć metody drawImage() .

var image = new Image();
image.onload = function(){
    ctx.drawImage(this, 0,0);
}
image.src = "someFile.SVG";

Obrazy SVG mają pewne zalety w stosunku do obrazów rastrowych, ponieważ nie stracisz jakości, bez względu na skalę, którą narysujesz na płótnie. Ale uwaga, może być nieco wolniejsza niż rysowanie obrazu rastrowego.

Jednak obrazy SVG mają więcej ograniczeń niż obrazy rastrowe.

  • Ze względów bezpieczeństwa nie można załadować żadnej zawartości zewnętrznej z obrazu SVG, do którego odwołuje się HTMLImageElement ( <img> )
    Brak zewnętrznego arkusza stylów, brak zewnętrznego obrazu przywoływanego w elementach SVGImage ( <image/> ), brak zewnętrznego filtra lub elementu połączonego xlink:href ( <use xlink:href="anImage.SVG#anElement"/> ) lub funcIRI metoda atrybutu ( url() ) itp.
    Ponadto arkusze stylów dołączone w dokumencie głównym nie będą miały żadnego wpływu na dokument SVG po odwołaniu do niego w elemencie HTMLImage.
    Wreszcie, żaden obraz nie zostanie wykonany wewnątrz obrazu SVG.
    Obejście: Przed odwołaniem się do elementu HTMLImage musisz dołączyć wszystkie elementy zewnętrzne do samego SVG. (w przypadku obrazów lub czcionek należy dołączyć wersję dataURI zasobów zewnętrznych).

  • Element główny ( <svg> ) musi mieć ustawione atrybuty szerokości i wysokości na wartość bezwzględną.
    Jeśli użyjesz długości względnej (np. % ), Przeglądarka nie będzie w stanie wiedzieć, co to jest względna. Niektóre przeglądarki (Blink) spróbują zgadnąć, ale większość po prostu zignoruje Twój obraz i nie narysuje niczego bez ostrzeżenia.

  • Niektóre przeglądarki skazą płótno po narysowaniu obrazu SVG.
    W szczególności Internet Explorer <Edge w każdym przypadku i Safari 9, kiedy <foreignObject> jest obecny na obrazie SVG.

Podstawowe ładowanie i odtwarzanie wideo na płótnie.

Płótno może służyć do wyświetlania wideo z różnych źródeł. Ten przykład pokazuje, jak załadować film jako zasób pliku, wyświetlić go i dodać proste kliknięcie przełącznika odtwarzania / pauzy na ekranie.

To pytanie z odpowiedzią na pytanie stackoverflow Jak wyświetlić wideo przy użyciu tagu canvas HTML5 pokazuje następujący przykładowy kod w akcji.

Po prostu obraz

W przypadku płótna wideo jest tylko obrazem. Możesz narysować go jak dowolny obraz. Różnica polega na tym, że wideo może być odtwarzane i ma dźwięk.

Uzyskaj płótno i podstawową konfigurację

// It is assumed you know how to add a canvas and correctly size it.
var canvas = document.getElementById("myCanvas"); // get the canvas from the page
var ctx = canvas.getContext("2d");
var videoContainer; // object to hold video and associated info

Tworzenie i ładowanie wideo

var video = document.createElement("video"); // create a video element
video.src = "urlOffVideo.webm"; 
// the video will now begin to load.
// As some additional info is needed we will place the video in a
// containing object for convenience
video.autoPlay = false; // ensure that the video does not auto play
video.loop = true; // set the video to loop.
videoContainer = {  // we will add properties as needed
     video : video,
     ready : false,   
};

W przeciwieństwie do elementów graficznych filmy nie muszą być w pełni ładowane, aby mogły zostać wyświetlone na płótnie. Filmy zawierają również szereg dodatkowych zdarzeń, które można wykorzystać do monitorowania stanu filmu.

W takim przypadku chcemy wiedzieć, kiedy wideo jest gotowe do odtworzenia. oncanplay oznacza, że załadowano wystarczającą ilość wideo, aby odtworzyć część, ale może nie wystarczyć do końca.

video.oncanplay = readyToPlayVideo; // set the event to the play function that 
                                  // can be found below

Alternatywnie możesz użyć oncanplaythrough który uruchomi się, gdy oncanplaythrough wystarczającą ilość wideo, aby można go było odtworzyć do końca.

video.oncanplaythrough = readyToPlayVideo; // set the event to the play function that
                                         // can be found below

Używaj tylko jednego zdarzenia canPlay, a nie obu.

Zdarzenie może odtwarzać (równoważne obciążeniu obrazu)

function readyToPlayVideo(event){ // this is a referance to the video
    // the video may not match the canvas size so find a scale to fit
    videoContainer.scale = Math.min(
                         canvas.width / this.videoWidth, 
                         canvas.height / this.videoHeight); 
    videoContainer.ready = true;
    // the video can be played so hand it off to the display function
    requestAnimationFrame(undateCanvas);
}

Wyświetlanie

Wideo nie będzie odtwarzane na płótnie. Musisz narysować go dla każdej nowej ramki. Ponieważ trudno jest ustalić dokładną liczbę klatek na sekundę, a kiedy się pojawiają, najlepszym rozwiązaniem jest wyświetlenie wideo w trybie 60 klatek na sekundę. Jeśli liczba klatek na sekundę jest niższa, w renderuj tę samą klatkę dwa razy. Jeśli liczba klatek jest wyższa, nie ma nic, co mogłoby zobaczyć dodatkowe klatki, więc po prostu je ignorujemy.

Element wideo jest tylko elementem obrazu i można go narysować jak każdy obraz, można skalować, obracać, przesuwać wideo, odzwierciedlać go, przyciemniać, przycinać i wyświetlać tylko części, rysować dwa razy za pomocą globalnego trybu złożonego aby dodać efekty takie jak rozjaśnienie, ekran itp.

function updateCanvas(){
    ctx.clearRect(0,0,canvas.width,canvas.height); // Though not always needed 
                                                     // you may get bad pixels from 
                                                     // previous videos so clear to be
                                                     // safe
    // only draw if loaded and ready
    if(videoContainer !== undefined && videoContainer.ready){ 
        // find the top left of the video on the canvas
        var scale = videoContainer.scale;
        var vidH = videoContainer.video.videoHeight;
        var vidW = videoContainer.video.videoWidth;
        var top = canvas.height / 2 - (vidH /2 ) * scale;
        var left = canvas.width / 2 - (vidW /2 ) * scale;
        // now just draw the video the correct size
        ctx.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
        if(videoContainer.video.paused){ // if not playing show the paused screen 
            drawPayIcon();
        }
    }
    // all done for display 
    // request the next frame in 1/60th of a second
    requestAnimationFrame(updateCanvas);
}

Podstawowa kontrola pauzy w odtwarzaniu

Teraz mamy załadowane wideo i wyświetlamy wszystko, czego potrzebujemy, to kontrola odtwarzania. Zrobimy to jako przełącznik kliknięcia na ekranie. Gdy wideo jest odtwarzane, a użytkownik klika, wideo jest wstrzymane. Po wstrzymaniu kliknięcie wznawia odtwarzanie. Dodamy funkcję, aby przyciemnić wideo i narysować ikonę odtwarzania (trójkąt)

function drawPayIcon(){
     ctx.fillStyle = "black";  // darken display
     ctx.globalAlpha = 0.5;
     ctx.fillRect(0,0,canvas.width,canvas.height);
     ctx.fillStyle = "#DDD"; // colour of play icon
     ctx.globalAlpha = 0.75; // partly transparent
     ctx.beginPath(); // create the path for the icon
     var size = (canvas.height / 2) * 0.5;  // the size of the icon
     ctx.moveTo(canvas.width/2 + size/2, canvas.height / 2); // start at the pointy end
     ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 + size);
     ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 - size);
     ctx.closePath();
     ctx.fill();
     ctx.globalAlpha = 1; // restore alpha
}    

Teraz zdarzenie pauzy w odtwarzaniu

function playPauseClick(){
     if(videoContainer !== undefined && videoContainer.ready){
          if(videoContainer.video.paused){                                 
                videoContainer.video.play();
          }else{
                videoContainer.video.pause();
          }
     }
}
// register the event
canvas.addEventListener("click",playPauseClick);

streszczenie

Odtwarzanie wideo jest bardzo łatwe przy użyciu płótna, dodawanie efektu w czasie rzeczywistym jest również łatwe. Istnieją jednak pewne ograniczenia dotyczące formatów, sposobu grania i wyszukiwania. MDN HTMLMediaElement to miejsce, w którym można uzyskać pełne odniesienie do obiektu wideo.

Po narysowaniu obrazu na płótnie możesz użyć ctx.getImageData aby uzyskać dostęp do ctx.getImageData w nim pikseli. Możesz też użyć canvas.toDataURL aby zrobić zdjęcie i pobrać je. (Tylko jeśli wideo pochodzi z zaufanego źródła i nie powoduje skażenia płótna).

Uwaga: jeśli wideo ma dźwięk, wówczas odtwarzanie go również odtworzy dźwięk.

Miłego filmowania.

Uchwyć płótno i zapisz jako wideo webM

Tworzenie wideo WebM z ramek obszaru roboczego i odtwarzanie w obszarze roboczym, przesyłanie lub pobieranie.

Przykład przechwytywania i odtwarzania płótna

name = "CanvasCapture"; // Placed into the Mux and Write Application Name fields of the WebM header
quality = 0.7; // good quality 1 Best < 0.7 ok to poor
fps = 30; // I have tried all sorts of frame rates and all seem to work
          // Do some test to workout what your machine can handle as there
          // is a lot of variation between machines.
var video = new Groover.Video(fps,quality,name)
function capture(){
    if(video.timecode < 5000){ // 5 seconds
         setTimeout(capture,video.frameDelay);             
    }else{
         var videoElement = document.createElement("video");
         videoElement.src = URL.createObjectURL(video.toBlob());
         document.body.appendChild(videoElement);
         video = undefined; // DeReference as it is memory hungry.
         return;
    }
    // first frame sets the video size
    video.addFrame(canvas); // Add current canvas frame
}
capture(); // start capture

Zamiast wkładać wiele wysiłku, by zostać odrzuconym, jest to szybka wstawka, aby sprawdzić, czy jest do zaakceptowania. Poda pełne szczegóły, jeśli zostaną zaakceptowane. Obejmują także dodatkowe opcje przechwytywania dla lepszych szybkości przechwytywania HD (usunięte z tej wersji, mogą przechwytywać HD 1080 przy 50 fps na dobrych komputerach).

Zostało to zainspirowane przez Wammy, ale jest to kompletne przepisanie z metodologią kodowania podczas pracy, co znacznie zmniejsza pamięć wymaganą podczas przechwytywania. Może przechwytywać dane o ponad 30 sekund lepiej, algorytmy obsługi.

Uwaga ramki są zakodowane w obrazach webP. Tylko Chrome obsługuje kodowanie płótna webP. W przypadku innych przeglądarek (Firefox i Edge) konieczne będzie użycie zewnętrznego kodera webP, takiego jak Libwebp JavaScript. Kodowanie obrazów WebP przez JavaScript jest wolne. (obejmie dodanie surowej obsługi obrazów webp, jeśli zostanie zaakceptowane).

Koder webM inspirowany Whammy: A Real Time Javascript WebM

var Groover = (function(){
    // ensure webp is supported 
    function canEncode(){
        var canvas = document.createElement("canvas");
        canvas.width = 8;
        canvas.height = 8;
        return canvas.toDataURL("image/webp",0.1).indexOf("image/webp") > -1;
    }
    if(!canEncode()){
        return undefined;
    }    
    var webmData = null;
    var clusterTimecode = 0;
    var clusterCounter = 0;
    var CLUSTER_MAX_DURATION = 30000;
    var frameNumber = 0;
    var width;
    var height;
    var frameDelay;
    var quality;
    var name;
    const videoMimeType = "video/webm"; // the only one.
    const frameMimeType = 'image/webp'; // can be no other
    const S = String.fromCharCode;
    const dataTypes = {
        object : function(data){ return toBlob(data);},
        number : function(data){ return stream.num(data);},
        string : function(data){ return stream.str(data);},
        array  : function(data){ return data;}, 
        double2Str : function(num){
            var c = new Uint8Array((new Float64Array([num])).buffer);
            return S(c[7]) + S(c[6]) + S(c[5]) + S(c[4]) + S(c[3]) + S(c[2]) + S(c[1]) + S(c[0]);
        }
    };    
   
    const stream = {
        num : function(num){ // writes int
            var parts = [];
            while(num > 0){ parts.push(num & 0xff); num = num >> 8; }
            return new Uint8Array(parts.reverse());
        },
        str : function(str){ // writes string
            var i, len, arr;
            len = str.length;
            arr = new Uint8Array(len);
            for(i = 0; i < len; i++){arr[i] = str.charCodeAt(i);}
            return arr;
        },
        compInt : function(num){ // could not find full details so bit of a guess
            if(num < 128){       // number is prefixed with a bit (1000 is on byte 0100 two, 0010 three and so on)
                num += 0x80;
                return new Uint8Array([num]);
            }else
            if(num < 0x4000){
                num += 0x4000;
                return new Uint8Array([num>>8, num])
            }else
            if(num < 0x200000){
                num += 0x200000;
                return new Uint8Array([num>>16, num>>8, num])
            }else
            if(num < 0x10000000){
                num += 0x10000000;
                return new Uint8Array([num>>24, num>>16, num>>8, num])
            }            
        }
    }
    const ids = { // header names and values
        videoData          : 0x1a45dfa3, 
        Version            : 0x4286,
        ReadVersion        : 0x42f7,
        MaxIDLength        : 0x42f2,
        MaxSizeLength      : 0x42f3,
        DocType            : 0x4282,
        DocTypeVersion     : 0x4287,
        DocTypeReadVersion : 0x4285,
        Segment            : 0x18538067,
        Info               : 0x1549a966,
        TimecodeScale      : 0x2ad7b1,
        MuxingApp          : 0x4d80,
        WritingApp         : 0x5741,
        Duration           : 0x4489,
        Tracks             : 0x1654ae6b,
        TrackEntry         : 0xae,
        TrackNumber        : 0xd7,
        TrackUID           : 0x63c5,
        FlagLacing         : 0x9c,
        Language           : 0x22b59c,
        CodecID            : 0x86,
        CodecName          : 0x258688,
        TrackType          : 0x83,
        Video              : 0xe0,
        PixelWidth         : 0xb0,
        PixelHeight        : 0xba,
        Cluster            : 0x1f43b675,
        Timecode           : 0xe7,
        Frame              : 0xa3,
        Keyframe           : 0x9d012a,
        FrameBlock         : 0x81,
    };
    const keyframeD64Header = '\x9d\x01\x2a'; //VP8 keyframe header 0x9d012a
    const videoDataPos = 1; // data pos of frame data header
    const defaultDelay = dataTypes.double2Str(1000/25);
    const header = [  // structure of webM header/chunks what ever they are called.
        ids.videoData,[
            ids.Version, 1,
            ids.ReadVersion, 1,
            ids.MaxIDLength, 4,
            ids.MaxSizeLength, 8,
            ids.DocType, 'webm',
            ids.DocTypeVersion, 2,
            ids.DocTypeReadVersion, 2
        ],
        ids.Segment, [
            ids.Info, [
                ids.TimecodeScale, 1000000,
                ids.MuxingApp, 'Groover',
                ids.WritingApp, 'Groover',
                ids.Duration, 0
            ],
            ids.Tracks,[
                ids.TrackEntry,[
                    ids.TrackNumber, 1,
                    ids.TrackUID, 1,
                    ids.FlagLacing, 0,     // always o
                    ids.Language, 'und',   // undefined I think this means
                    ids.CodecID, 'V_VP8',  // These I think must not change
                    ids.CodecName, 'VP8',  // These I think must not change
                    ids.TrackType, 1,
                    ids.Video, [
                        ids.PixelWidth, 0,
                        ids.PixelHeight, 0
                    ]
                ]
            ]
        ]
    ];    
    function getHeader(){
        header[3][2][3] = name;
        header[3][2][5] = name;
        header[3][2][7] =  dataTypes.double2Str(frameDelay);
        header[3][3][1][15][1] =  width;
        header[3][3][1][15][3] =  height;
        function create(dat){
            var i,kv,data;
            data = [];
            for(i = 0; i < dat.length; i += 2){
                kv = {i : dat[i]};
                if(Array.isArray(dat[i + 1])){
                    kv.d = create(dat[i + 1]);
                }else{
                    kv.d = dat[i + 1];
                }
                data.push(kv);
            }
            return data;
        }
        return create(header);
    }
    function addCluster(){
        webmData[videoDataPos].d.push({ i: ids.Cluster,d: [{ i: ids.Timecode, d: Math.round(clusterTimecode)}]}); // Fixed bug with Round
        clusterCounter = 0;
    }
    function addFrame(frame){
        var VP8, kfS,riff;
        riff = getWebPChunks(atob(frame.toDataURL(frameMimeType, quality).slice(23)));
        VP8 = riff.RIFF[0].WEBP[0];
        kfS = VP8.indexOf(keyframeD64Header) + 3;
        frame = {
            width: ((VP8.charCodeAt(kfS + 1) << 8) | VP8.charCodeAt(kfS)) & 0x3FFF,
            height: ((VP8.charCodeAt(kfS + 3) << 8) | VP8.charCodeAt(kfS + 2)) & 0x3FFF,
            data: VP8,
            riff: riff
        };
        if(clusterCounter > CLUSTER_MAX_DURATION){
            addCluster();            
        }
        webmData[videoDataPos].d[webmData[videoDataPos].d.length-1].d.push({
            i: ids.Frame, 
            d: S(ids.FrameBlock) + S( Math.round(clusterCounter) >> 8) +  S( Math.round(clusterCounter) & 0xff) + S(128) + frame.data.slice(4),
        });
        clusterCounter += frameDelay;        
        clusterTimecode += frameDelay;
        webmData[videoDataPos].d[0].d[3].d = dataTypes.double2Str(clusterTimecode);
    }
    function startEncoding(){
        frameNumber = clusterCounter = clusterTimecode = 0;
        webmData  = getHeader();
        addCluster();
    }    
    function toBlob(vidData){
        var data,i,vData, len;
        vData = [];
        for(i = 0; i < vidData.length; i++){
            data = dataTypes[typeof vidData[i].d](vidData[i].d);
            len  = data.size || data.byteLength || data.length;
            vData.push(stream.num(vidData[i].i));
            vData.push(stream.compInt(len));
            vData.push(data)
        }
        return new Blob(vData, {type: videoMimeType});
    }
    function getWebPChunks(str){
        var offset, chunks, id, len, data;
        offset = 0;
        chunks = {};
        while (offset < str.length) {
            id = str.substr(offset, 4);
            // value will have top bit on (bit 32) so not simply a bitwise operation
            // Warning little endian (Will not work on big endian systems)
            len = new Uint32Array(
                new Uint8Array([
                    str.charCodeAt(offset + 7),
                    str.charCodeAt(offset + 6),
                    str.charCodeAt(offset + 5),
                    str.charCodeAt(offset + 4)
                ]).buffer)[0];
            id = str.substr(offset, 4);
            chunks[id] = chunks[id] === undefined ? [] : chunks[id];
            if (id === 'RIFF' || id === 'LIST') {
                chunks[id].push(getWebPChunks(str.substr(offset + 8, len)));
                offset += 8 + len;
            } else if (id === 'WEBP') {
                chunks[id].push(str.substr(offset + 8));
                break;
            } else {
                chunks[id].push(str.substr(offset + 4));
                break;
            }
        }
        return chunks;
    }
    function Encoder(fps, _quality = 0.8, _name = "Groover"){ 
        this.fps = fps;
        this.quality = quality = _quality;
        this.frameDelay = frameDelay = 1000 / fps;
        this.frame = 0;
        this.width = width = null;
        this.timecode = 0;
        this.name = name = _name;
    }
    Encoder.prototype = {
        addFrame : function(frame){
            if('canvas' in frame){
                frame = frame.canvas;    
            }
            if(width === null){
                this.width = width = frame.width,
                this.height = height = frame.height
                startEncoding();
            }else
            if(width !== frame.width || height !== frame.height){
                throw RangeError("Frame size error. Frames must be the same size.");
            }            
            addFrame(frame);   
            this.frame += 1;
            this.timecode = clusterTimecode;
        },        
        toBlob : function(){
            return toBlob(webmData);
        }
    }
    return {
        Video: Encoder,
    }
})()


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow