2

I've got a blob of audio data confirmed to play in the browser but fails to play after storing, retrieving, and conversion of the same data. I've tried a few methods without success, each time returning the error:

Uncaught (in promise) DOMException: Failed to load because no supported source was found

Hasura notes that bytea data must be passed in as a String, so I tried a couple things.

Converting the blob into base64 stores fine but the retrieval and playing of the data doesn't work. I've tried doing conversions within the browser to base64 and then back into blob. I think it's just the data doesn't store properly as bytea if I convert it to base64 first:

// Storing bytea data as base64 string

const arrayBuffer = await blob.arrayBuffer();
    const byteArray = new Uint8Array(arrayBuffer);
    const charArray = Array.from(byteArray, (x: number) => String.fromCharCode(x));
    const encodedString = window.btoa(charArray.join(''));

   
    hasuraRequest....
      `
      mutation SaveAudioBlob ($input: String) {
        insert_testerooey_one(
          object: {
            blubberz: $input
          }
        ) {
          id
          blubberz
        }
      }
      `,
      { input: encodedString }
    );

// Decoding bytea data
const decodedString = window.atob(encodedString);
    const decodedByteArray = new Uint8Array(decodedString.length).map((_, i) =>
      decodedString.charCodeAt(i)
    );

    const decodedBlob = new Blob([decodedByteArray.buffer], { type: 'audio/mpeg' });
    const audio4 = new Audio();
    audio4.src = URL.createObjectURL(decodedBlob);
    audio4.play();

Then I came across a Github issue (https://github.com/hasura/graphql-engine/issues/3336) suggesting the use of a computed field to convert the bytea data to base64, so I tried using that instead of my decoding attempt, only to be met with the same error:

CREATE OR REPLACE FUNCTION public.content_base64(mm testerooey)
 RETURNS text
 LANGUAGE sql
 STABLE
AS $function$
  SELECT encode(mm.blobberz, 'base64')
$function$

It seemed like a base64 string was not the way to store bytea data, so I tried converting the data to a hex string prior to storing. It stores ok, I think, but upon retrieval the data doesn't play, and I think it's a similar problem as storing as base64:

// Encoding to hex string

const arrayBuffer = await blob.arrayBuffer();
    const byteArray = new Uint8Array(arrayBuffer);

    const hexString = Array.from(byteArray, (byte) =>
      byte.toString(16).padStart(2, '0')
    ).join('');

But using the decoded data didn't work again, regardless of whether I tried the computed field method or my own conversion methods. So, am I just not converting it right? Is my line of thinking incorrect? Or what is it I'm doing wrong?

I've got it working if I just convert to base64 and store as a text field but I'd prefer to store as bytea because it takes up less space. I think something's wrong with how the data is either stored, retrieved, or converted, but I don't know how to do it. I know the blob itself is fine because when generated I can play audio with it, it only bugs out after fetching and attempted conversion its stored value. Any ideas?

Also, I'd really like to not store the file in another service like s3, even if drastically simpler.

Kyle Truong
  • 2,545
  • 8
  • 34
  • 50

1 Answers1

0

Here is how I managed to get it to work with an image that is stored as hex in a bytea column in Hasura:

async function hexDump(file: Blob) {
  // gotten from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Example
  function hex(buffer: ArrayBuffer) {
    return [...new Uint8Array(buffer)]
      .map((x) => x.toString(16).padStart(2, '0'))
      .join('');
  }

  return new Promise<string>((res) => {
    const reader = new FileReader();
    reader.onload = () => {
      if (typeof reader.result !== 'string' && reader.result) {
        res(hex(reader.result));
      }
    };
    reader.readAsArrayBuffer(file);
  });
}

Then I can call the function like that:

hexDump(internalImage).then((img) => {
  setImage(`\\x${img}`);
});

To decode the image from hex I am using the following code:

const hexToBase64 = (hexInput: string): string => btoa(
      (hexInput.replace('\\x', '').match(/\w{2}/g) ?? [])
      .map((a: string) => String.fromCharCode(parseInt(a, 16)))
      .join(''),
    );

Which can be used like so:

`data:image;base64,${hexToBase64(internalImage)}`

Hope it works with an image as well!

Tim
  • 1
  • 1