简体   繁体   中英

How to download file from Cloud Storage [Firebase Hosting]

I would like to make a web app using Firebase Hosting.

  1. make audio file using Cloud text to speech API
  2. upload that audio file to Cloud Storage
  3. download that audio file from Cloud Storage to a web browser

I passed step 1 and 2, but have a trouble with step 3. I followed this turorial.

https://firebase.google.com/docs/storage/web/download-files

I deployed my Firebase project and tested my app. I could upload audio file to Cloud Storage, but I couldn't download it. I looked at browser's console, but I couldn't find any error message. There was no message in browser's console.

Could you give me any advice? Thank you in advance.

This is my main.js

'use strict';

// Saves a new message on the Cloud Firestore.
function saveMessage() {
  // Add a new message entry to the Firebase database.
  return firebase.firestore().collection('messages').add({
    text: messageInputElement.value,
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).catch(function(error) {
    console.error('Error writing new message to Firebase Database', error);
  });
}

// Checks that the Firebase SDK has been correctly setup and configured.
function checkSetup() {
  if (!window.firebase || !(firebase.app instanceof Function) || !firebase.app().options) {
    window.alert('You have not configured and imported the Firebase SDK. ' +
        'Make sure you go through the codelab setup instructions and make ' +
        'sure you are running the codelab using `firebase serve`');
  }
}

// Checks that Firebase has been imported.
checkSetup();

// Shortcuts to DOM Elements.
var messageInputElement = document.getElementById('text');
var submitButtonElement = document.getElementById('download');

// Saves message on form submit.
submitButtonElement.addEventListener('click', saveMessage);


// Create a reference from a Google Cloud Storage URI
var storage = firebase.storage();
var gsReference = storage.refFromURL('gs://advan********8.appspot.com/audio/sub.mp3')
gsReference.getDownloadURL().then(function(url) {
  // This can be downloaded directly:
  var xhr = new XMLHttpRequest();
  xhr.responseType = 'blob';
  xhr.onload = function(event) {
    var blob = xhr.response;
  };
  xhr.open('GET', url);
  xhr.send();

}).catch(function(error) {
  // A full list of error codes is available at
  // https://firebase.google.com/docs/storage/web/handle-errors
  switch (error.code) {
    case 'storage/object-not-found':
      console.log('storage/object-not-found')
      break;

    case 'storage/unauthorized':
      console.log('storage/unauthorized')
      break;

    case 'storage/canceled':
      console.log('storage/canceled')
      break;

    case 'storage/unknown':
      console.log('storage/unknown')
      break;
  }  
});

This is index.js (Cloud Functions)

const functions = require('firebase-functions');
var admin = require("firebase-admin");
admin.initializeApp();
const textToSpeech = require('@google-cloud/text-to-speech');

exports.myFunction = functions.firestore
  .document('messages/{id}')
  .onCreate((change, context) => {
        const client = new textToSpeech.TextToSpeechClient();
        async function quickStart() {

            // The text to synthesize
            const text = 'Hello world';

            // Construct the request
            const request = {
                input: {text: text},
                // Select the language and SSML voice gender (optional)
                voice: {languageCode: 'en-US', ssmlGender: 'NEUTRAL'},
                // select the type of audio encoding
                audioConfig: {audioEncoding: 'MP3'},
            };


            var bucket = admin.storage().bucket('adva********.appspot.com');
            var file = bucket.file('audio/sub.mp3')
            // Create the file metadata
            var metadata = {
                contentType: 'audio/mpeg'
            };

            // Performs the text-to-speech request
            const [response] = await client.synthesizeSpeech(request);
            return await file.save(response.audioContent, metadata)
            .then(() => {
              console.log("File written to Firebase Storage.")
              return;
            })
            .catch((error) => {
              console.error(error);
            });

        }
        quickStart();
    });

This is index.html

<!--./advance/index.html-->
<!doctype html>
<html lang="ja">
   <head>
      <meta name="robots" content="noindex">

      <title>音読アプリ アドバンス</title>
      <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <link href="https://fonts.googleapis.com/css?family=M+PLUS+Rounded+1c&display=swap" rel="stylesheet">

      <style> 
            #text {width: 100%; height: 300px; font-family: 'M PLUS Rounded 1c', sans-serif; font-size: 22px;}

            #download {font-family: 'M PLUS Rounded 1c', sans-serif; font-size: 28px;}

      </style>
   </head>

   <body>
      <textarea id="text" class="form-control" name="text" placeholder="ここに英文を入力してください。" maxlength="3000" minlength="1"></textarea>
      <br>
      <div style="text-align:center">
        <input id="download" class="btn btn-primary" type="submit" value="音声をダウンロード">
      </div>

      <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
      <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
      <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

      <!-- Import and configure the Firebase SDK -->
      <!-- These scripts are made available when the app is served or deployed on Firebase Hosting -->
      <!-- If you do not want to serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup -->
      <script src="/__/firebase/7.14.3/firebase-app.js"></script>
      <script src="/__/firebase/7.14.3/firebase-auth.js"></script>
      <script src="/__/firebase/7.14.3/firebase-storage.js"></script>
      <script src="/__/firebase/7.14.3/firebase-messaging.js"></script>
      <script src="/__/firebase/7.14.3/firebase-firestore.js"></script>
      <script src="/__/firebase/7.14.3/firebase-performance.js"></script>
      <script src="/__/firebase/7.14.3/firebase-functions.js"></script>
      <script src="/__/firebase/init.js"></script>

      <script src="scripts/main.js"></script>
   </body>
</html>

browser's developer tool's Network tabxhr

The problem is that you are likely trying to download before the file is created by your cloud function, since you are running the cloud function as a event trigger that automatically runs every time a document is created, but at the same time you are trying to download that file on your front end. This lack of syncronism creates this odd behaviour you are seeing.

In order to fix that there are a couple of things you should do:

  • Convert your cloud function to a http triggered function instead of a event triggered function.

This will make it so your function can be invoked by your front end after the creation of the document and before you are trying to download it, it could look like this:

exports.myFunction = (req, res) => {
    //you can get the id of the document sent on the request here
    const id=req.body;
    ...
};

Also, you might want to check this documentation for more details on this type of trigger

  • Create a download function and add all your code for the download to it and add synchronicity to your operations on your front-end.

With this your code will execute in the proper order, and you main.js will look like this:

// Saves a new message on the Cloud Firestore.
function saveMessage() {
  // Add a new message entry to the Firebase database.
  firebase.firestore().collection('messages').add({
    text: messageInputElement.value,
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  })
  .then(function(docRef){
    var obj = {  
      method: 'POST',
      body: docRef.id
    };
    //calls function that adds to storage
    fetch("YOUR_FUNTION_URL_HERE", obj).then({
        //actually downloads
        download();
    }).catch(error) {
      console.error('Failed to call cloud function', error);
    });
  }).catch(function(error) {
    console.error('Error writing new message to Firebase Database', error);
  });
}

function download(){
    var storage = firebase.storage();
    var gsReference = storage.refFromURL('gs://advan********8.appspot.com/audio/sub.mp3')
    gsReference.getDownloadURL().then(function(url) {
      // This can be downloaded directly:
      var xhr = new XMLHttpRequest();
      xhr.responseType = 'blob';
      xhr.onload = function(event) {
        var blob = xhr.response;
      };
      xhr.open('GET', url);
      xhr.send();

    }).catch(function(error) {
      // A full list of error codes is available at
      // https://firebase.google.com/docs/storage/web/handle-errors
      switch (error.code) {
        case 'storage/object-not-found':
          console.log('storage/object-not-found')
          break;

        case 'storage/unauthorized':
          console.log('storage/unauthorized')
          break;

        case 'storage/canceled':
          console.log('storage/canceled')
          break;

        case 'storage/unknown':
          console.log('storage/unknown')
          break;
      }  
    });
}

// Checks that Firebase has been imported.
checkSetup();

// Shortcuts to DOM Elements.
var messageInputElement = document.getElementById('text');
var submitButtonElement = document.getElementById('download');

// Saves message on form submit.
submitButtonElement.addEventListener('click', saveMessage);

NOTE : This is all untested but it will be a good starting point for your to start the changes necessary to your code.

I hope someone may still find this useful. A hack around is to create an express server/lambda/cloud function to download file and send it back to the browser via the download method. Check bellow code

const express = require("express");
const app = express();
var fs = require("fs");
const request = require("request");

app.get("/", (req, res) => {
  const { filename, fileUrl } = req.body;

  const file = fs.createWriteStream("filename");
  request.get(fileUrl).on("response", function (response) {
    var pipe = response.pipe(file);
    pipe.on("finish", function () {
      res.download(filename, function (err) {
        if (err) {
          console.log(err); // Check error if you want
        }
        fs.unlink(yourFilePath, function () {
          console.log("File was deleted"); // Callback
        });
      });
    });
  });
});

app.listen(3000, () => console.log("Server ready"));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM