skip to content

Build a Multiplayer Soundboard Using Firebase

What’s more fun than a soundboard? A soundboard you can play with people around the world in real time! In this episode, David East will teach us how to build one using Firebase!

Full Transcript

Click to toggle the visibility of the transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

Jason: Hello, everyone. And welcome to another episode of Learn with Jason. Today on the show, we're bringing back David East! Hey, David, how are you doing?

David: Hey, I'm super-excited to be back here. I had so much fun last time. I had to come on again.

Jason: I had a blast last time too. See if we can turn it up a notch today by going even more ridiculous which I cannot wait for. For those not familiar with your work, do you want to give a little bit of background on yourself? David: My name is David East, as we have said. I work on Firebase. That's this thing. And I'm a developer advocate and I have been doing that for over six years, since April 2014. Since Firebase was a little startup before Google. And now Firebase were a long time is part of Google and we have been helping people build on Firebase for a long time now.

Jason: Nice. Yeah. Last time you were here, we looked at Firebase in a let's get started way. We set up a project, did some kind of rewrite stuff. And today we wanted to just go ahead and go full blown chaos. And so, what did you have in mind for what we can build together today? David: So, I was a huge fan of your I feel like your site rewrite. But it's been out there for quite a while. Now it's just your site. But what was once your site rewrite. And I really was a big fan of the sounds. I think that sound effects is just such an under-used part of websites. And so, your sound effects are beautiful. And I think we should just create a soundboard for all the audience in the chat to click on buttons and then in real-time, it starts playing the sounds for everybody. Which I'm not sure exactly -- there's so many different ways to handle this.

Jason: Sure. David: In terms of, do we queue up the sounds? Is it just a cacophony of noises? Like, there's so many ways. I'm very excited.

Jason: Yeah, yeah, I think we'll have to figure that out. Maybe poll the audience. If we put me on an alignment chart, I always to want fall in chaotic goods. I think the cacophony is where I'm looking to start. Let's see what we can get done. I guess to start, let's switch over. We'll look at pairing mode here. And while we're over here, take a look at LWJ.dev/live, we've got live captions going for the show. That is done courtesy of -- not courtesy of, thank you to White Coat Captioning. They're here. We've got Amanda in the chat with us today. Thank you very much. And that is made possible through sponsorships by Netlify, Fauna, Auth0 and Sanity. And that means a lot to me to be accessible. And follow David on Twitter. A lot of great content through here. Get your Firebase questions answered as long as with other general nonsense which is always very fun. David: Mostly general nonsense.

Jason: And today, we are going to be looking at Firebase. So, let's start wherever we should start. What's the first thing I should do to get this soundboard going? David: Okay, the first thing we should do is let's cover what we're going to use within Firebase to do this thing. And also, you will have a decision to make too. This is even more fun. So, right now you're at the -- we call this the Firesite internally. This is like the marketing, docs, like all the information for Firebase. And then if you go up to the top right, there's go to console. And this is the Fire console.

Jason: This is not the account I want to use. David: Yes. We went through that last time.

Jason: This other browser. David: And we're here!

Jason: Yeah. Let me go to my console. Wait, how do I get there? This is the console. Yes. David: I created a project and shared it with Jason. My project ID is David Learns with Jason. It's very good and accurate. And so, to the left is sort of your NAV of all of the things that one can do with Firebase. We are not going to do everything. Firebase can do a ton of things. I like to say that it's for developers and tools for marketers. And we're going to do some of the infrastructure for developers start today. And the first place I want us to focus on is within storage. So, this -- you've ever uploaded anything to a bucket, that's what this is. This is just a bucket. But this is a special bucket because what we can do is we actually can access this bucket in a serverless way. Which that's a very loaded term. But when I say that, I mean, like you just don't need to run your own server to talk to the bucket. So, there's no access control that you have to worry about on your own server. We can secure everything through rules which, if you remember, if anyone was here last time, or if you remember, Jason, when we secured the database. It's very similar to that. Same syntax and everything.

Jason: Okay. Cool. Yeah. But -- David: But our security model is super-easy. If you actually go to rules, it's already written.

Jason: If I go -- okay. Rules. Here we go. David: So, we have a read-only policy.

Jason: Okay. David: Hey, we allow reads. True. Everyone can read it. And we'll write a false because we have all of our files uploaded. We just lock that down. If you're doing uploads and user content, then this becomes more complicated.

Jason: Sure. David: But in our read-only world, this is easy.

Jason: Yeah. This is great. It's a special bucket because it's a holy bucket. That's correct. I thought you were teeing up a joke like this one is special because it's mine. ( Laughter ) But so, okay. So, in here we've got this cors.json. This is just anybody can fetch from this bucket? David: For the most part, you don't have to worry about this. But depending on what we are going to do today, I just wanted to be as prepared as possible.

Jason: Okay. David: Cors.json. You tend not to run into these problems unless you have an open system. But like I said, I didn't know where we were going to put things today. Where is the JSON? Cool.

Jason: Cool. In here, we have the sounds. These are the sounds from my website. And so, to make sure that everybody can hear today, I'm pretty sure I have everything going. We have... boop, boop! Bhuu -- David: I have to ask you, how much time did you spend recording those sounds?

Jason: So little. I just beatboxed for like 5 minutes. And now -- David: That is a great answer. Because if that would have been me, I would have been like, three weeks.

Jason: Yeah. David: Yeah. So, I really -- I appreciate people who don't like obsess over things. And so, I do. I obsess over everything. So, this -- but I don't want to. So, when I hear people, no, just like 5 minutes. I'm like, teach me your ways.

Jason: So, the secret I found out is to wait until the last second for everything so that you don't have time to obsess over it. I don't know if it's a healthy way to do things. But it does tend to get them done. David: All right. I'm going to adopt that. I'm going to set myself timelines, not work at it at all. Then when it hits the timeline.

Jason: Yeah, 6 p.m. the day it's due. That's the best time to start. David: Okay. I'm gonna write that down.

Jason: But, yeah. So, we have sounds. And so, for any of these that we want to listen to, they're just pew! David: That's good, that's my favorite.

Jason: This is public. Like if we drop this into -- David: Yep.

Jason: Here, anybody can go and listen to that. This is really fun. That means what we're building, we don't have to deal with the sounds themselves. The sounds are ready. We have to deal with the thing that accepts the events and then distribute them around to everybody else. David: Yeah. I feel like this is a good teaching moment here too. The buckets are a little bit more complicated. But I like to boil it down to it's just folders and files. And I hadn't touched my computer. So, I just had to unlock my computer. It's went into like screen saver mode. Anyways. So, basically, it's folders and files. And then when you -- with that URL you see right there, it has that token. Like, query param there. And so, basically what's going on there is this URL is totally public for everyone. And all of the files in general are public. But they have this token. And so, they're non-guessable obfuscated files. The security access locks down who has access to it through what we call these GS URLs where basically through that folder path it controls access to the bucket. But if you have access to that token or this file, this whole URL, then anyone can see it. So, there is the ability to have a -- you could also revoke the tokens at any point. But none of the files are directly locked down. There's access to who gets token more so.

Jason: Got ya. David: That's a way to put it.

Jason: So, if I drop them off. David: Yeah.

Jason: It's public -- pew! Pew! Pew! It's auto-playing that. David: Honestly, this would be good if we just played it over and over again. Just the whole time.

Jason: Well, I suspect that's what's gonna happen as soon as we make this public. David: Everyone's just picking the air horn.

Jason: Okay. David: There are other buttons, people.

Jason: So, let's make a thing here. Let's -- let me get my -- get things over here. Actually, sorry, you were walking us through the services. You talked about storage. What else are we going to use today? David: Storage is good for this type of content. It's binary storage, a piece of content, media, storage. There's a lot of crazy things you can do with it, but for the most part it's media, images, video. But for the actual communication, the whole real-time backing, buckets are different than databases. And so, we have two databases that we offer. There's the real-time database which is the sort of the OG database where people say, oh, I set up a Firebase. That's really what they mean.

Jason: Okay. David: And Cloud Firestore is our new database. They are both fully supported by us. One is not the old version and the new version. They are two different databases with two different purposes. But we get -- this is a decision point where -- because we're going to pick one to use to do the signaling of, you know, what sounds get played.

Jason: Okay. Given I have no real reference for using these separately, I'm gonna leave that decision up to you. Or I guess chat, if you have strong opinions, we've got two people talking about Firestore. AUDIENCE: Says Firestore. They're both good for this case. They don't get into where they deviate too much. If you're building something fun, you're not going to run into the flex points for either one.

Jason: Gotcha. David: It's not that big of a deal. Let's go with -- let's see here. We only got -- I'm going to go with the use Cloud Firestore for a nail polish site. That's good. We have two Firestores in the chat. Let's go Firestore.

Jason: Firestore, here we go David: We're going to create a Firestore. You have this problem last time as well. You have this problem because of me. I'm going to create the Firestore. We had this problem because you were not the admin and we had to switch the project.

Jason: Oh, that's right. David: I'm going to put you as an editor.

Jason: How dare you. David: I did it for your own good. I didn't want you to get into the point where you were like, David, I broke the project. And you're like, oh, man. So, I didn't -- I didn't want to overwhelm you. It wasn't a trust thing. There's really not much you can do totally wrong with -- there's no credit card attached to this thing. So, if you refresh the page now, it will show a database. At least --

Jason: Database! David: Yes, here we go.

Jason: Okay. So, now we have -- so, if we talk about the way this is gonna work. Right? If we start from the UI, what I'm picturing is just a grid of buttons. David: Yes, me too.

Jason: Because we don't have design. We can just do words. David: Yeah.

Jason: And it will just say the name of the thing. And then when you click it, we are going to play that sound in the browser. That part makes sense to me. That is very familiar. We have a link to the file. When we click it, we're going to be able to create an audio element and play that sound. All good. That makes sense. The other thing that we have to do is we have to take that event and send it to all other connected clients in real-time. David: Yes.

Jason: And so, that's the part that obviously we're going to be using Firebase for. And that's the part that I think I have the most questions around. So, are we storing these? Or using Firebase as a pass through? David: That depends how you want to do it. A lot of people use a PubSub-style thing here and sending a message over. There's no persistences sis tent storage. Firebase is effectively what a lot of people think of like a real-time PubSub system. But the storage is there and the sync of those messages is also there in the database. Which is the really hard part. So, it depends on how you want to organize it. There's a lot of creative ways you can do here. You can have people technically create tracks if you click a bunch of buttons. And that's a queue of things.

Jason: That would be pretty funny, but, yeah. David: That might be a little bit complicated for this. What we could do is we could just create a queue. And every time someone clicks something, it gets added to the queue.

Jason: Okay. David: And if you don't want it to make an ear explosion, after it's in the queue, it's like a playlist of your sounds.

Jason: Okay. Maybe we can -- if we set it up as a queue, if we have time, we can allow for an option where you can -- you can choose between ear explosion or loosely-regulated chaos. David: You could do it where you are a queue and that gets added. And then if -- and then once played, it will update that it's been played. And so, you can filter out. You can say, get me the queue. So, queries for this are also in real-time. Queue is get me everything that has not been played. And that will filter everything out. You could do it in the way where you say, hey, like, give me everything on this list. And once I show up to the page, you can play the queue in like historical context. And then you could also play it all at once because you're like, I have all the files. So, let's just -- a lot of ways to do this.

Jason: Okay. So, yeah. Let's set up this queue. Let's make this work. What's the first thing that I need to do here? David: So, this works similar to a noSQL database, a lot of them, popular ones, Mongo and whatnot. Collections and documents. A collection is a list of documents. Create a collection, call it queue track sounds, ear bleed --

Jason: Ear bleed... David: And then so ID, for this case, since we're going to be using a bunch. I guess D is -- this is a really important part of a NoSQL database because what you name things with an ID really affects how easy to do to access data down the line.

Jason: Okay. David: And so, there's no like one-size-fits-all. But in this case, because we are going to be asking people to be like click, click, click, click, click, the ID needs to be generated. It needs to be something where it's not going to be inform collisions. In this case, we call these autoIDs.

Jason: Okay. David: so, on the client, we'll generate the auto IDs as well. And so, at this point, we need to I guess use the ID of the storage. Let me go into -- I'll pull it up on mine to look at it. I guess the ID will be like the file name. So, thing to play. Like song, track.

Jason: Like -- and can -- David: Air horn, that would be great.

Jason: Dot MP3? David: It's a bad key in general because you could have two air horn.mp3s in that folder. We could do a fully qualified path. I think that's overkill.

Jason: For what we're doing today, we're okay. David: Mostly as a caveat for someone watching this later can think about that. Yeah. And then played.

Jason: Okay. And so, this is what we'll enter when someone clicks the button. They're gonna -- they're going to enter an ID, a file name and played which will automatically set defaults to start. David: Yes.

Jason: And then our query is going to pull everything that's played false, play it, and update it to played true. Okay. David: So, this is the queue path. So, I guess save it. And I guess I can put -- for contrast show like the other way of doing it would be a little crazier. I guess there's another way of doing it. But another way is you could have just the clobber effect. Which is probably the easiest, but the craziest. You would effectively say, we have a thing called active sound. And when they click on the button, we just write to active sound and that gets played.

Jason: Okay. David: But the crazy thing about that active sound location, every time it gets updated, it's going to keep getting clobbered over.

Jason: Right. David: You're never guaranteed order in that way. If you truly want chaos where you don't know what's going to get played, when you opened up the browser, who clicked at what time, like the network sending things --

Jason: Right, right. David: It's going to be absolute race conditions galore on multiple levels.

Jason: Pandemonium. David: You could do it that way as well. And code-wise, that's like a one-liner.

Jason: But this sounds like fun. So, let's do it this way. Because I think we can create that pandemonium -- David: Right. We could still do this. We could still have this queue right to the active sound to play as well.

Jason: But this is kind of fun -- what's fun about this, though, by having this queue, what's also going to happen is we're going to get a history. And that means we could do something like pulling the most popular sounds. We could pull the things that have been played most often. Theoretically we could add more fields to it later and track like, people in Australia really liked boop or something like that. You know, there's lots of -- lots of fun ways that we could play with this. I think for now this is probably good enough. And I also just want to make sure we don't run out of time. David: Exactly.

Jason: I want to have plenty of time for the chat to just go ham on this. David: And the last thing I will bring up before we get into the code is authentication. We did authentication last time. And so --

Jason: Okay. David: We have some experience there. It's pretty easy to do. But in terms of -- and this is for this session. How much do we care about security in terms of like who can -- who can boop and air horn? Because if we -- it's a bit of a -- there's a bit of a problem there in terms of like who can write to a database. So, if you have a database that's fully open --

Jason: So, what I think in this particular case, because we're tightly controlling what's possible to do, there's only a certain number of files. We'll just bail if they put a file name that doesn't exist in there and no one can upload files. I think we should treat this booping soundboard as an open borders democracy. Anybody can make as much noise as they want. We welcome all to the chaos with open arms. David: So, one thing we could do is we would need to have another field for UID.

Jason: Okay. David: And we can authenticate people, but we can authenticate them anonymously. And so, that means --

Jason: Okay. David: Before they know anything. They don't know, but they're going to get authenticated.

Jason: Okay. David: Underneath the scenes. But they get a session ID, effectively. And that way when we save item welcomes whether we have time to secure it or not, you can say, hey, this person even though they're anonymous, they technically own all of their own data and no one can go into their records and mess with it. Because if you --

Jason: I got you. David: If you don't have any sort of access, it becomes difficult to control that queue.

Jason: I see what you mean. Okay. David: This is more so like for our hack session, if we don't do this, this is fine. But just in terms of people -- people really like to see the reasoning where you structure databases. And this is one of the key factors of it.

Jason: Yeah. So, what I should call this UID, right? David: And that can just be -- you can call it Jason right now. It doesn't matter. It's a string.

Jason: It will be a string? David: Yeah, it's just going to be a generated format from us.

Jason: I got you. Cool, cool. David: That's the basics of -- of the way you would secure it and store it and everything. And at this point, yeah, we're good.

Jason: Okay. David: To start hacking on the client.

Jason: Yeah. Let's do it. So, to hack on the client, I have -- let me move into here and then we'll make -- this is going to create a folder for me when I create a project? Or should I create a folder? David: You mean like with Firebase like when you do Firebase knit or anything?

Jason: Yeah. David: Oh, create a folder and then do a knit inside the folder.

Jason: Okay. We're going to call this Firebase-realtime-soundboard. Let's get into it. So, so, we would need to add code. Is that right? That's correct. What's it called? David: Oh, Firebase/tools.

Jason: I thought I installed this last time. Apparently I didn't. David: Yeah, no modules is a scary world.

Jason: Let me do this. I'm going to get init in here and then run-in vm use 14. Maybe that's the problem. David: Yeah, that happens all the time.

Jason: Nope. Don't have it. It's npm install global Firebase tools. David: You can install it locally if you want to.

Jason: We're already here. David: I avoid installing things globally as little as possible due to things like that. I work on a project where all my TypeScript code was failing. And I couldn't figure out why. It was because this one project had TypeScript not as a Dev dependency. It was going off the global dependency. And this one version of Node which I had to switch back to, had a 2-year-old TypeScript version. It was trying to compile it with a syntax that it was a nightmare. Literally 4 hours of my life I will never get back.

Jason: All right. So, this is Firebase. And I think I can go right in here. David: Yeah, this is to do all the Firebasey things. And we're good.

Jason: I'm going to log in, I'm going to Firebase init. David: so, we're going to want to do -- go to Firestore -- I don't think we -- do storage -- I don't think we will need it. If you want to see something cool. Actually, don't do emulators. There's no storage emulator yet.

Jason: Got it. David: There's a Firestore. Just as a little call-out too. Emulators are ways to run Firebase locally on your -- like multiple Firebase services locally on your machine.

Jason: Cool. David: You do like no latency. We just launched an authentication one so you can do you will of a your authentication code locally. It's super cool. CI systems, CI/CD is much easier now. And question give you a local UI to manage everything. Similar to how you're doing it in the console.

Jason: Linda, so answer the question about the terminal color scheme? I honestly have no idea. I think I just chose one. Minimal. David: There you go.

Jason: I don't know. David: I do the same thing. I don't know.

Jason: Yeah, I don't think I actually chose any of this. I think this was the default. David: That always seems very overwhelming to me.

Jason: Dark ground color preset is my color preset. But, yeah. So, okay. I've got Firestore and storage selected. Just press enter? David: Yeah.

Jason: Okay. We're going to use an existing project, right? David: Yes. It should give you -- yes, David Learns Jason. And then now, just hit enter for these.

Jason: Okay. David: Enter. And enter.

Jason: All right. Let's take a look. David: All right. Boom!

Jason: Beautiful. David: Is that your own little alias for code to say teach that.

Jason: It uses a different VSCode settings profile to the font is bigger and the helpers that get noisier are turned off. David: That's dope. Yeah.

Jason: Make life a little bit easier. David: Yep.

Jason: I did just set up Starship. And I have no idea if it's changed. This start is starship. And I really like it. This is the defaults of starship with a few limited exceptions. I have my dot files in here somewhere. Where is it? No idea. There you go. Dot file. That'll link you to my dot files where you can see how I set up starship and all that stuff. But, yeah. Let's look at the configured things. David: Yes.

Jason: The project you set up is David learn with Jason -- David: Firebase RC is your projects. This is a manifest of all the projects that you can switch to, deploy and do different things to. We just have the one.

Jason: The one. And then here? David: The default. This is our rules -- sorry, not the rules. Firebase.JSON is for multiple products. This will tell the CLI if you're trying to upload rules for Firestore. If you're trying to upload rules or indexes for creating more complex querying. Where they are. Read them and then upload them to the console. And the rules are the same way. And the VSCode to get it highlighted. Which is the best way to write them is locally. So --

Jason: This one, Firebase? David: Yep. And but we will not be uploading them -- we might do it for fire, but not for storage. For storage, we're good with what we wrote in the console.

Jason: Okay. David: That's what the files are, writing the rules or telling the files where the other files are. Which is all a config file really is.

Jason: Good. Good, good. Now we have done that, we are just going to create our project, right? David: Yep.

Jason: And how do we want to do this? We probably want to use -- I don't know, what do you think? David: I loved how you used -- we React. I'm a big React fan. That was easy and you were well-versed with the template APIs and all that. I like that.

Jason: So, let's go here and how am I going to copy this because we already have some stuff in the folder? I think what I can do is let's get clone, learnwithjason/demo-base. And put that into site. Yeah, sure. Why not? David: Why not?

Jason: And we'll get around the problem where it would not let me clone into a non-empty directory. I can remove site.get. And if we go back over here, now we have a site. Good. So, in this site, if I move into it, and we'll do an npm install. That is gonna give us a pretty straightforward -- David: Okay. You have some Eleventy going on, night.

Jason: Yeah. Basically it gives us a wrapper so we don't have to really write anything. I'll update the rest of that later. For now, we will comment it out. And what I can do then down here is we have this index.HTML which is just an empty HTML file for us. We can put whatever we want in here. David: Nice.

Jason: We can start by saying Firebase-realtime-soundboard. David: And that's it.

Jason: We're done! If I can remember how this works. We're going to run Netlify Dev, I think. Yeah, there we go. So, let me open this up over here. Okay. So, now we've got our Firebase site kind of set up. It's not really doing anything yet. But we do have -- here, let me open index.HTML again. There it is. And so, if I, you know, if I make changes to it, like we can... we'll see those coming. David: Nice. Nice.

Jason: So, I guess the first thing would be to figure out just the very basics of our soundboard. So, we can set up like a div and we'll call this soundboard. Maybe we'll just leave it at soundboard. David: There we go.

Jason: And then for each sound, we're going to set up a button. We'll give it a class of sound. And that should actually be fine. And then each one of these is gonna have a name so we can go with like air horn. David: And we'll be able to fill these in dynamically as well --

Jason: Yes. David: So, that way it will just grab the board and fill the children.

Jason: So, I can do something like for soundboard, we'll do -- we'll make it margin like 3rem auto. Make a max-width of 90 vertical width units. Set the width to 500. And I don't know, what do you think? Do a background color of I think we have a pink in here that we can use. David: I like it.

Jason: And then for each button, we can set a background of background. David: The boops are getting intense.

Jason: Oh, good. We're getting buried. Let me scroll this up so people can see the code we're writing here. David: It keeps scrolling.

Jason: And so, we'll set the background to be a background color. We'll set a color to be -- we'll make it like black, I think. And then we can do a border. None. And maybe -- maybe we'll set like padding of 1rem, a margin of -- actually, let's not use margin. We'll use -- we'll use grid instead. So, we'll make this. David: Some grid gap going on.

Jason: Grid. We can set the gap to be 1rem. And we'll set the justify-content to center. And why don't we also set the align items to center. And we'll give this some padding as well. So, how about that? How is that gonna look? Cool. That works. David: Looks good.

Jason: We probably need to control these a little bit. So, let's go with a width -- no. We need grid template columns. Grid template columns and -- David: Get yourself going the right way.

Jason: And auto-fit. David: Ooo! Getting fancy.

Jason: Is this right? David: No, auto-fit is good.

Jason: Is that right? David: I think that's right.

Jason: Duplicate -- David: I've just started cramming on grid myself. I have been doing things wrong for so long that I started doing it the right way.

Jason: Holy buckets, did that had just work? This part didn't work, though. Oh, these need to be display block. David: Or a button's not --

Jason: How dare you. David: Why is it not gapping?

Jason: Let's find out. David: Sorry.

Jason: Oh, oh, I know what it is. I know what it is. I have -- David: Oh, my gosh!

Jason: Okay. David: That was just cruel.

Jason: That would have got me for a long time. So, that also means I think this was working. It does work. And what if... although, that's not right. 1fr. There. I like that. That works. David: There you go.

Jason: And we'll see what we get from all of these. And then each of these is now a clickable button. Good. I'm happy. Let me set the border to be like 2pix solid black so it looks a little more buttony. David: Nice.

Jason: It doesn't look great. It will be -- David: This is perfection.

Jason: And then what we'll do is down here is we will set up a little bit of JavaScript and we will say -- we'll set up a function that's like playSound. Or addSoundToQueue. How about that? And we'll get an event. Don't need to prevent default because it's just a button without anything going on. David: But that's what superstitious Devs do. I want -- I once not prevent default and the whole world collapsed.

Jason: Just do stuff. And for now, we'll just alert pew, pew, pew, pew! Okay. So, now we need to actually apply this. So, we'll say, document.query selector. Chat is frozen. Why did y'all break the chat? Okay. Fine. Let me refresh it. David: Whose fault is that?

Jason: That's certainly not my problem. What did you do? That's not the thing. That's -- David: I love how you have the -- that is -- I couldn't stream it. Mine would just be that. A head.

Jason: Test it again. You better? Quick breaking my stuff. Okay. So, now what we can do is add eventListener. We're going to find out if I'm doing this right at all. And we'll say, click. And it will attach, add sound to queue. David: Boop fest and break things. That's a good lifestyle.

Jason: Nope. That doesn't work. What have I done? I think I -- I think I need adult supervision here... David: Oh, yeah, yeah. You have to forEach them. But you can't do that, because that's not going to be an actual array.

Jason: What? David: You have to do online 41, if you do array.from

Jason: I don't believe you. David: You can't do forEach. You can for each? Since when? I have been writing around it forever.

Jason: I think it had query selector. David: I thought it returned as a node list. For each? I don't believe you.

Jason: It might be a new thing. Let's see. David: Yeah, does this get updated. For the longest time, you used to have to array. This is my old, old web Dev, like not having any conveniences in life. Like I can loop over in a Node list? I don't think so. I have to count each one by hand.

Jason: I don't know when this got added. There's no history on it. David: Maybe was thinking of map.

Jason: But either way, we're excited. It's all working. David: It's good. Maybe I was thinking of map. I remember Node list being quite simplistic. And so I, I always array.from it. It's always a real array. But if we have forEach, stick to that.

Jason: This is good enough. David: Yes, this is good.

Jason: We can work with that. I'm also going to make this H1 text-align: Center just to put it over the top of our soundboard. David: Nice.

Jason: And now we've got ourselves just a delightful -- David: As delightful as can be.

Jason: Soundboard experience. Beautiful. Absolutely beautiful. Brings a tear to my eye. David: So, now the thing we need to do is we need to set up the SDKs, the libraries for Firebase. We'll just do that over the CDN.

Jason: Oh, okay. David: Yeah. Like if -- unless you feel like -- just do it over the CDN. Don't have to mess with Node modules. So, if you go into the gear up there. Top left.

Jason: Gear, got it. David: Gear. And go to project settings. That's lot we can copy and paste here. Boom. Here you go. These URLs. So, we'll start with the -- start with the top one.

Jason: Just the top one. David: Just the top one. We'll come back. But let's just do the top one for now just to sort of do it piece-by-piece.

Jason: Okay. Now David: Now let's go and copy and paste that on another line and mess with one file. We're at Firebase-app, now change the next one to Firebase-storage. And lastly, do one more and then Firestore.

Jason: Okay. Gotcha. David: So, we're going with the very naive loading method here. You can be very smart with the way you load Firebase. That is not gonna happen today.

Jason: What we are doing instead is we are smashing it over the head with a hammer until it bends to our will. David: Pretty much.

Jason: That's how I like to do things here. David: That's how we do things here.

Jason: Boop fest and break things, as the chat said. David: That's another one of my disclaimers. This is not the only way. But this is the way we're going today. Now we have all the files. Now, if you go back to the site, the Firebase console, that is.

Jason: Okay. David: And copy the Firebase config.

Jason: Okay. Just this? Or all of it? David: So, you can just copy -- I usually just copy the object literal. But you can copy the variable. You just need the actual -- the JSON-esque I think this the actual object itself.

Jason: Okay. David: So, now up at the top, just kind of, yeah, paste that const --

Jason: Wait, I already did copy that part. There we go. David: Yeah. Boom. Now what we're going to do is create another variable called FirebaseApp. So, const Firebase, because Firebase is on the window. Initialize app.

Jason: Like that? David: Yeah and then FirebaseConfig. This will tell Firebase where to talk to your specific back end.

Jason: Okay. David: So, if you wanted to switch to another one for whatever reason, it's multi-tendency, all inform stuff --

Jason: You could do Firebase 2 with a different config. David: Actually, what you can do is the second parameter of the initialized app, you could give it a name and you could have two apps to speak with.

Jason: Got it. David: And Firebase.apps is an array of your apps.

Jason: Okay! David: This is about 95% of people don't name multiple apps.

Jason: And just to check, this is all public. This is stuff you can publish in your repo. David: Yes. We did work with Stripe on some of the extensions we did. So, you can add like Firebase check out-- sorry, Stripe checkout and invoicing really easily. And they call their keys -- sorry, they call them publishable keys. And I was like, oh, that's a good word.

Jason: Yeah. David: Because all this stuff is publishable.

Jason: Cool. Cool, cool, cool. David: Yeah. All this stuff, totally fine. The rules is what keeps everything secure.

Jason: Gotcha. David: Now we can talk to -- let's start with storage. The first thing we'll do is create a variable called storage. And just say FirebaseApp.storage and call it as a function. Yep. So, now we have an instance of storage. It's a singleton underneath the hood, calling it a bunch of times isn't going to do anything. That's a question -- people are like, oh, no, am I creating new memory pressure. No, it's fine. And now we need to create a reference to the sounds location. So, create -- call it soundref or sounds and, yeah, soundsRef is good. And then you say storage.ref. And then now many we're tapping into that folder in the bucket, and that is sounds.

Jason: And is it with or without the slash? David: I've never done the trailing prefix, or the slash, so, I don't know. But that should work.

Jason: Okay. David: And now, we want to list everything in that bucket. Which we could technically also mirror this data into Firestore. And so, that could be a bit more dynamic. But just because -- this will save us some time. And because these are the only sounds we're working with, it kind of works well in this case. We can list everything. So, go to a new line. And since we are the top level, we won't -- we'll have to promise chain this because top level weight isn't ready for us yet.

Jason: I mean, I can wrap this thing in a -- David: If you want to wrap it in a function to give yourself some async await. I'm very excited for top level await to be a thing.

Jason: I have -- I have my hopes and I have my concerns. I do think it's gonna be good. Like, I do think that it will be helpful. David: It definitely have craziness to it. But I'm tired of wrapping things in functions and seeing what are all the bad things that can happen.

Jason: Now we are going to get our list. This should be sounds, right? David: Yes. What you can do is say soundsRef, yea, await. I always forget await. I'm like, it should know.

Jason: How dare you not read my mind. David: How dare you not know what I need to await. Seriously, all my bugs are that. Sounds ref --

Jason: Sorry, I'm just picturing you. You and your computer, how dare you! David: I could send a video I did on the Firebase user channel. There's a space on the channel I go back and write await. And say on the video, I always do this. And it's true. I run into the bug. What do you mean it's a promise? Oh, because I didn't wait for it. Every time.

Jason: I'm awaiting sounds ref.

David: List all.

Jason: List call. David: Don't call is results. Let's tear gassed structure, say items inside of the on the. That's one of the -- you can get back prefixes like sub folders and items, everything at that folder level. All we have in this case are items. Let's do a sanity check, this is the kind of developer I am, and let's console log, items. Look at you labeling it.

Jason: I can't function without. Okay. So, here's our items. David: Boom. And so, that right there is -- should be --

Jason: Location, bucket, path. David: Yep, these I believe are all references. And on the prototype change should be a method called get download URL. And when we share that the link in the chat, same link that it's going to give us back. And so --

Jason: So, if I do -- hold on. So, if I do sounds, items.map, item. And then return item.get -- what was it -- download URL? David: It's asynchronous, it's going to be a promise. So, you're going to need to do some promise.all --

Jason: Oh, yeah, you're right. Let's not. Right. Let's not do that. So, we'll get promises. And then -- David: Yeah.

Jason: And then we'll get sounds equals await promise.all. Promises. Promises, promises. David: I always like to create a available for promise.all that's promises. And I don't know, just because it sounds funny to write promises in your codebase. I never try --

Jason: I typo'd this. Is it capital URL? David: Oh, yeah. It's capital all URL. Yeah. Yeah.

Jason: Okay. David: I like the lower case of URL. But that's not in our style guide.

Jason: There we go. David: Now we have all the stuff.

Jason: Do we get other information? David: We do, but we stripped it by doing it.

Jason: We can fix this. Watch me go. Watch me move. David: You write the code.

Jason: So, we'll do a URL. And then we will send back also the name. And the name would be in -- oh, I lost that part. Do you remember what it's called? Like item dot David: I'm trying to remember. This is why -- I use TypeScript. Now I'm like, I am so lost right now. Item dot --

Jason: What is that nonsense? You stopping that right now. Okay. So, here's my items. Items dot -- David: Let's see here.

Jason: Location.path? David: I think it's path, yes.

Jason: Okay. Now do I need to do a get path or something like that? David: I think if you do ref.two string I think gives you the whole path. Let's just do that. ToString. And I think it might be -- gosh, I feel so dumb right now. What are we getting back here? That's the full GS URL. It's the full path. We want the last bit. Maybe it might be .path. Not as a -- yeah, I think that's it.

Jason: Undefined. David: No, it's undefined. What is it?

Jason: Well, so, this one is what's actually in the -- David: Yeah, but I think it is underneath the --

Jason: So, I'm cheating. But it works. David: I'm trying to run through the docs quick. Which is funny, when I scroll through the docs. This is how you do it in videos! Yes, past David, I know you know how to do this.

Jason: Tony knows how to do this. .child, and then is it the string path? Like that? David: No, I think what he's saying is that --

Jason: I did that all the way wrong. David: Oh, my gosh. I feel so dumb here. Where is the -- root.parent --

Jason: Oh, that returns the item ref. That's not what we're after. David: Do .name.

Jason: Item.name? David: Yes. There we go.

Jason: Exactly what we needed. David: It was the easiest one Occam's razor wins again.

Jason: And now out of there -- air horn, boop, perfect. Now that we have our sounds, what I can do is turn off the logging here and instead we will do sounds.forEach sound. And then we're going to -- let's get the container. Let's call it soundboard. Let's be descriptive here. We'll be document.quantum dotsSelecter, soundboard. And then for each one of these, we will create a button. That button will be document.createElement button. And -- David: People are pointing out that we have the promise on the thing. But you think we can get away from with stripping out -- we want to resolve the promise. We want to create a global or like a store of the URLs.

Jason: What? David: It's because -- so, let's create the button first and then we'll get there.

Jason: Okay. Okay. David: I need to hammer you with tons and tons of information as you write code. Because that's the best way to do it.

Jason: Okay. And then I think we also want to do like a button -- hold on. Mdn data attribute JavaScript. I want to dataset. David: Yes. I always forget that as well.

Jason: And it would be sound.name. David: Oh, you have dot button for createElement.

Jason: Oh, yeah. You're right, you're right. David: Yeah, you're thinking classes in there. Look at you.

Jason: And then we'll do button.inner. Text is name. And then soundboard.appendChild-button. Okay. So, then I can get rid of these. David: Yep.

Jason: And we got close. We had a good run. So, I typo'd something. David: I don't think you put the value --

Jason: Whoops. David: What did you do? Sound.name. You want to know an interesting -- you can do text content which you think is more compliant to XS and other performance things. But they have subtle differences that go beyond the scope of this 5 second explanation. But so, now we have that filled. So, the way I think we can do this is we want to have like and, you know, almost like a little store. You can think about like a Redux store. You want in memory sitting around like a store of all the files and the -- or all the sounds. So, you want like a hash of like the name, the same thing that you're assigning to the dataset, to the data dash, as the key. And then the value being the sound. And then that sort of just gets populated on bootup, on page load. And then when people click, and we say, hey, what's the active sound that's coming through? We do a lookup on that. Boom. That's the one that's played. Or the next one we're playing. Something like that.

Jason: Got it. Okay. So, we will set hash item -- David: And someone is asking if we know audio elements. I don't think we need audio elements because we'll just be able to create new audio classes or objects.

Jason: Okay. So, I'm just gonna grab this out. And then we'll set the hash to be the name. And then we'll set these values in here to also be the name. And that means I don't need this anymore. David: Destructuring for the win.

Jason: You're not going to clean up for me? Promise. Here's my reducer. Oh. I guess -- David: I know, you did .reduce. And I'm like, I'm out. You're alone on this one. I would have done a forEach.

Jason: What did I miss? What did I screw up here? David: Oh, I never use reduce.

Jason: There. That was it. David: Oh, you just had it missing.

Jason: So, I still screwed something up. Object is out iterable. David: I think --

Jason: What are you yelling at me for? Oh. Now it's not an object anymore. David: Yes.

Jason: But we actually shouldn't need this anymore, right? David: Well, it's still gonna return a promise to --

Jason: Why are you doing that to me? David: You could do --

Jason: This is a promise? David: Yeah, URL would still be a promise. What you could do --

Jason: Let's just do it. David: Is that gonna work?

Jason: Okay. David: Reduce, missing --

Jason: Don't you lie to me. David: Don't you lie to me.

Jason: It's not a function. What is sounds, then? David: Yeah, what is sounds?

Jason: Oh, my goodness. My -- my JavaScript language not working is killing me right now. So -- David: That's a promise. Oh, yeah, because you return it as async, so, it's not a promise.

Jason: That's right. David: You have to write await everywhere possible.

Jason: Still not doing what I want. David: It's still -- you have sounds inside of sounds. So, just console.log sounds without the destructuring. And I think at this point the returning object --

Jason: This isn't destructured. So, this -- I should be getting the hash. The accumulator. The value. David: Yeah.

Jason: So, my accumulator is an empty object. And so, what I should be setting up is -- oh. Oh. I know what I'm doing wrong. So, we're gonna set that value and then we'll return the whole hash. David: hm...

Jason: And then we get one. But it doesn't do the rest. Why? David: Like I said, any time someone's like, well, you can use reduce for that. And I'm like, you can.

Jason: You can do whatever you want. David: Yeah. David East uses a forEach.

Jason: Okay. So, this part worked. David: Spread and reduce is what we're getting in the --

Jason: Spread. No, I don't to spread. I need to get this to wait. Which means I need to -- crap. Oh, there -- why -- like, why... this is where async starts to break my brain. This is a promise. If I await this, it breaks. Which means -- because we're -- so, we're getting -- we're getting the value now that I want. We've got our hash. We've got the name. This is really small, isn't it? Let me make this bigger. We've got the name, that's working. We've got the URL, we need to resolve that promise. David: Yes, your thing is a promise.

Jason: Yes. So, I need to... we don't even I think need to await that anymore. Right? So, we don't need to await that anymore. But I do need to await these. Which means -- David: There's a good point. I think you can do it this way, you can say const URL equals await until download URL. Because then it should run through the function --

Jason: I don't think that's going to work. David: Yeah, I don't think it waits.

Jason: Okay. I have -- I have another idea that is probably not -- not the best practice. But! David: This is like six items, so, it's like --

Jason: I know, this is such a -- this is not the right way to do it. But what we're going to do is I'm going to return the item. David: Yeah, I see what you're doing. Yeah.

Jason: No. I hate this. That's not right. So, what I'm... so, we've got our reducer. Ahem! What? What? But I can't do that outside of the reducer. That won't work -- David: He's saying next to -- below const name.

Jason: Like, I can do this, but that still won't work. David: Right. I think -- I've ne'er done async await in a reduce.

Jason: So, that would mean that this needs to be async. Then what we'll get back is a single promise. Okay. So, if I await that. David: Await this.

Jason: I get back a single air horn. So, the problem that we're getting is that by doing it this way, it's -- it breaks the reducer. So, instead, what we need is for -- David: We need like a for of and iterate over each one and they can you can resolve inside of it.

Jason: You're saying like for item of items -- David: Yeah.

Jason: Then I want to get this. And then we'll just have a like a const sounds. David: Yes.

Jason: Equals an object like this. Okay. And then for each of these, we'll do sounds, name. Equals name URL. Okay. So, let's get rid of this. David: I think. But now I don't trust anything anymore.

Jason: I trust no one. So, that worked, but I screwed something up. David: But it's an object now. Now you're doing a forEach.

Jason: That's fine. David: You need the object.keys.

Jason: Object.values. David: Dot values. Sorry. Old school.

Jason: We're getting there. All right. So, that was a -- that was a long way around far short walk. And a good reminder that sometimes you don't need a reducer. David: Yeah. This is a -- I don't know if you know Jake Archibald on the web DevRel team at Google, he's a staunch don't use reduce. And I'm going to send him this. ( Laughter )

Jason: But no, this is it, right? No mental acrobatics, didn't have to make the code more confusing. We had async stuff? Line, just do it in line. That makes sense. However, it's been so long, that I forgot this syntax. This is very nice. David: I just started using for of again. It's been nice.

Jason: Okay. So, now what we've done is we're gonna have the -- the button is going to be event.target. And then we can get the sound name will be event dot -- no. Button.dataset.name. And so, now we should be able to alert the sound name. Okay. Let's try that again. Air horn. David: Boom.

Jason: Power down. Okay. So, now we have all -- this is the plumbing, right? David: Yes.

Jason: The sounds are loaded in, displaying buttons, now this is the Firebase part. David: Yes.

Jason: When we get this, send this to Firebase. We have 22 minutes. Can we do it? David: Do you want to do it all in one line right now? Because we can.

Jason: Let's do it. David: Write Firebase.

Jason: Oh. David: Firebase app. I prefer doing it this way. It's the same thing. Firebase.firestore as a function.

Jason: Okay. David: Dot collection. And then for, what do we call it? Queue.

Jason: Okay David: And then call .add.

Jason: It's an object. David: It's an object. What was our set?

Jason: It was... David: File name?

Jason: Let's double check, make sure we get it right. Firestore, and for our queue, it's filename played, false. David: We can skip UID for now if you want.

Jason: Okay. David: So, do filename and that's the sound.

Jason: No, that's not. It's going to be sounds -- soundName.URL. David: Well, actually, you don't need that. We can do the signaling -- you know what? It's fine this way too. It's all a matter of architecture. You could technically do that. You're storing the end results of state here versus I was thinking you would store the thing that signals to the client what to play. So, you're basically asking who has control.

Jason: Oh, okay. David: In our argument, in our case, for our 20 minutes left, it doesn't matter. I'm wasting our time. Let's keep moving. I need to get out --

Jason: I like where you're going from. Yes, we're going to send it to soundname and leave the UID off. David: Yea.

Jason: It's okay. What's going to happen, it's not going to alert anymore. I'm going to click a few of these. David: And then when we go to the console. Yep. There's all our new entries.

Jason: There we go. David: We don't have it ordered. Actually, we need to do a time stamp. This also is very easy.

Jason: Okay. David: Go back to the code. The U ID -- sorry, the autoIDs are not ordered.

Jason: Okay. David: Time is fine. You would do -- timestamp is good too. Do Firebase dot -- I can't remember this off the top of my head. TypeScript helps me. Firebase.firestore, don't call it as a function. FieldValue,.timestamp. Or is it server.timestamp?

Jason: Let's find out! David: I think it's .server .timestamp.

Jason: You are correct. It is not timestamp. David: Yes. Lower case S, server, and capital T timestamp as a function. It's a place holder value. You can say new date.get time, or something like that. But what it is, that's server time. And so, it's not hacked in on the client. So, that it's always good to rely on server time and we make it easy to do that.

Jason: Okay. David: And so, in this case, it wouldn't matter. But for real life. So, it yeah. Now we know what time. We can query by time.

Jason: Okay. David: So, what was added? So, now what we want to do is we want to be able to get the things that should be played.

Jason: Yes. David: So, we're gonna write a -- so, somewhere on page load or something like that --

Jason: Well, we're already running this in init, right? David: Below Firebase app, we need to write a query. So, write const query.

Jason: Okay. So, this is the -- this is the last bit. Const query. David: And so, FirebaseApp.firestore as a function. Same thing did. That collection of queue. And then went to -- let's see here. What is it? Oh, my gosh. This is for TypeScript, as I said -- Firestore, query -- I think in this one --

Jason: Would it be list all again? David: No, it's not list all. I am trying to find the order by one. I'm blanking off the top of my head. Actually, what I do is, when I have to do orderBy, what I immediately do is I think about how it works in each one of the databases and then I can't remember which one it is. So, I'm like, I could name a bunch of things. Order it. So, okay. So, this one is .orderBy. And we want to orderBy by timestamp. OrderBy is a function. And then timestamp.

Jason: Like that, okay. David: Yes. And this will be in ascending order, I believe. And so, now we have this query. And then now you can on the line below it write query .on -- lower case O -- capital S, onSnapshot. And this is an observer. So, this is the realtime magic here. Call this as a function. And this is going to return to you a snapshot. So, oh, no, no, sorry. It's a observer as in the callback function inside of onSnapshot. Because it's a realtime stream. So, yeah, you write snapshot. Every time there's a change, every time people are clicking stuff, boom.

Jason: Got it, got it, got it. David: This snapshot is at that point in time, you know, the snapshot of what that collection looked like. And so, this has an array of docs on it. So, snapshot.docs.

Jason: Like that? David: Yep.

Jason: Okay. What we should seen then is as soon as I load this page, we will get something in here. David: These are document -- yes. What you need to do is this is a pure array in docs. You can actually say docs.map.

Jason: Okay. So, we'll say queue = snapshot.docs.maps. Dock. David: And then doc.data as a function and that will give you back the actual queue as an array.

Jason: Here's our queue. And there was a question about like clearing the queue in Firebase. But because we ordered by timestamp, it ignores anything that doesn't have a timestamp, right? David: Yep.

Jason: Cool. That's perfect. That works exactly the way we wanted. Good. All right. So, now we have our sound effects. And with 15 minutes left, I want to get these up. But actually, what I'm gonna do right now, because we -- we want to get all this stuff set up here. Is I'm going to -- David: You let people add to the queue.

Jason: Yes, let's people start adding to the queue. David: This is the scary part.

Jason: I didn't add anything I didn't mean to. And let's Git commit in progress. Sounds add to the queue, but don't play. Okay. So, let's Git create, learnwithjason/fire baste will be AUDIENCE: Linda was asking, how do you toggle something is played? Each record in the queue will have the ID. Right now we're looking at the ID, that's easy to fix. We can retain the ID. When something's been played, after it's been played. Which I think you can do something like that with the Web API to know when the file is finished playing. We can on that played callback right out to the database that it's been played.

Jason: Good, good. Okay. Our build command -- I think I'm -- David: Are we even building? You have your Eleventy thing going on.

Jason: That's going to be site/site. That's not going to work. So, I'm going to have to figure that out. I got to move this -- this Netlify.TOML to the root for us. And then we're just gonna set the base to be site and that should solve all of our problems. So, let's get add-all. Git commit. David: Config is just a file that knows where other files are. That's all it is.

Jason: Okay. So, let's get that. And then... we'll double check all of this. Base directory does not exist? I don't believe you. David: I don't believe you.

Jason: There we go. Now what? No site ID found in the current directory? How about now? There you go. There you go. David: Boom.

Jason: Okay. This one's published. Let's go look at it. Here's our realtime sound board. David: Nice.

Jason: It's pulling up stuff. We should see -- yeah. We have eight of them. So, now, chat. Go get it. Let's cause some chaos. And while you click this, we should see these start loading -- yep. Here we go. Everybody's in here, making a mess. David: This is dangerous

Jason: This is going to be so loud. David: I'm going to take my headphones off.

Jason: Look at it go. David: Okay. ( Laughter )

Jason: We got to get this built fast because otherwise we're going to have to wait for an hour and a half for the sounds to play. Now what we're doing, we've got the snapshot, we've got the queue. Now we need to play a sound and update its played status, right? David: Yes. What we need to do is whenever a sound comes through, we need to -- to take that file name and find the URL in that object we have in the global thing up top.

Jason: Okay. Just getting our -- make sure we're running again. We're good. Okay. David: Oh, my god. There's going to be a thousand. You get 55,000 writes to Firestore per day for free.

Jason: Good, let's rate limit David's account, everyone. David: Yeah. Thanks.

Jason: So, the thing that we're gonna need first is so we got the queue. We've got this collection. You said that we want to play it and then we want to update it. So, we need to do two things. Create and play audio element. David: Oh, my god. There's got to be a thousand. There is a thousand. I'm sorry, are you doing something? I'm just looking at that console.log.

Jason: Okay. So, to create and play an audio element, I'm just going go and look at -- David: Just do new audio. And then you do that object. And then you do audio .play.

Jason: Okay so, we've got audio equals new Audio. And then we need to make the audio -- the audio.source. David: no you can do it in the constructer.

Jason: Okay. David: You can do that not as an object, just as a string.

Jason: Okay. Perfect. Okay. So, then we're gonna do sounds. And it was -- our queue has -- David: We have to do this sorry for each. The audio is just going to be one track. So, you have to do queue.forEach.

Jason: Can we filter for unplayed here? David: We could do another -- orderBy -- you could do dot wear --

Jason: Played. David: Played equal -- yeah. I'm like, comma --

Jason: Like that. David: Parentheses. No, you play and then do string, equal, equal. And then false. Yeah.

Jason: Like that? David: Yeah. Just like that.

Jason: Okay. David: That will get us all the not played ones.

Jason: Okay. So, then what we can do is queue for each and we'll call this sound -- nope. I'm trying to give unique names to things so that we don't get confused. David: Yes, inside of there it's good to do that.

Jason: For each of these, we're going to get sounds, sound effect. David: Right.

Jason: Name. No. Dot name. Dot filename. And then dot URL. David: So, we did. They said they destroyed the quote rate which might be detrimental for us trying to -- yeah. They did.

Jason: All right, everybody. Stop booping. David: I don't know how long -- I'm like, can we finish now?

Jason: We'll find out, right? We'll see what happens. So, for each of these now, we're gonna get our -- our list here. And then -- David: Yes.

Jason: We're creating audio with that URL. David: So, let's -- let's go a little crazy here. Sorry, not go crazy here for testing. You can limit the amount that comes back. So, instead of getting the thousand, you could just limit to like one. Just so we're not --

Jason: Yeah, let's add like medium chaos. Let's do 10. David: There you go.

Jason: Okay. So, for each of these, we need to audio -- is it just the audio.play? David: It's just audio.play. It's not going to play them this way. It's going to play them all at once.

Jason: Yeah, that's fine. I think what's fun for this, once this is actually running, then as people hit buttons, it will be as if we're sitting at each other's computers mashing keys. David: All ten will play at the same time. Making that clear.

Jason: Yes. Obviously this is not what we want to do with 2,000 of these. David: I don't know, maybe it is.

Jason: I think when we're caught up with real-time, it will be fun because we'll be able to make these noises and everybody will be able to mash keys. Then we need to set this up where we have the sound effect. And now I need to update it to say, played. David: Yeah. So, one thing we need had to do here, when you call docs.map. We're just getting back the data. Which does not have the document's ID. So, doc.ID does. And so, we're losing that piece of information. And so, return an object. And what I like to do is just say, ID, yes, just like that. And say ID is doc.IDE. And then spread over doc.data. Yeah, just like that.

Jason: Like this? David: ID's just a property, not a method.

Jason: Yeah. David: Just like that, yeah.

Jason: Okay. So, now we have an ID. So, here then we would do like FirebaseApp. David: Yeah, dot Firestore.collection.

Jason: Firestore, collection -- David: And actually, you can say, not collection -- actually, we can do it this way, say .doc. And that is of the sound effect.id. That is the individual record now.

Jason: Okay. David: And then you could say, .update. And then as an object say played: True.

Jason: And that's all we need? David: Yep. There might be some race conditions because of the amount of ways we're doing things but I think that's good. I'm a little nervous just from the amount -- any time I see that many updates, I'm like, oooo...

Jason: Missing after argument list. I typo'd. David: Oh, it might just be the --

Jason: Oh, I just had -- I'm short one. David: you're short one parameter in parentheses yeah.

Jason: Quota exceeded. David: Quota exceeded.

Jason: Maybe what we can do just to demonstrate this. Go way back up here to where we only had a few of these. David: We also can do -- let me see here. I don't know how much time we have left. I could -- actually, I'm going to do this, one second. I'm gonna -- I am going to see what I can do here.

Jason: Copy this, copy property -- not like that, stores global variable. What's it stored as now? Okay. So... David: Come on. I'm trying to do a hack real quick if I can get it in time. I don't want to tell anyone because everyone's gonna break us.

Jason: Let's see. This, unfortunately, does not have the ID. We're going to be hosed. David: We're not hosed. I have a bit of a hack. One second. I'm going to create a new project. Seed it with -- with the new -- with the sounds and then we just use the config and all the code will work. We just switched the Firebase config, that is.

Jason: Oh, I got it. We'll just come back over here. And I'm gonna go to Firestore. David: So, I've created the new project. It's in the middle of creating. I'm going to send -- how do you want me to send you the config?

Jason: I don't know. Maybe Twitter DM, I guess? David: Twitter. Creating buckets. Come on, come on, come on, come on. Setting up rules. Because I'm just gonna upload all of those files again. And that way it should work. David got a call from DevOps today.

Jason: So, with 2 minutes remaining, really hopeful this is going to work. David: I think we can do it.

Jason: There was going to be a rate limit here. David: I didn't either. I'm not going to lie. This is -- hang on a second. Do, do, do -- all right. I'm doing so many things at once right now. Create.

Jason: And while you're getting that config, a quick shout here. Remember, the show is live captioned. � the show is live captioned, brought to you by White Coat Captioning, made possible think Netlify, Fauna, Sanity and Auth0 who help make things accessible which I appreciate. Check out the schedule. We have a lot of cool things coming up. Including on Tuesday, Cassie Evans is going to join us, SVG animations. Don't sleep on that schedule, y'all. It's going to be really, really, really good stuff. I see we are getting buried in boops. David, do you -- let's see. I'm gonna drop your Twitter one more time in here. David: Yeah.

Jason: Anywhere else that I should send people? David: I think -- I don't know. I feel like that's good. Twitter is good. Firebase is good. I don't want to get too greedy here. That is good.

Jason: Yeah. Go. And definitely go check out the Firebase docs. All right. Do you have a now config for me? David: I do. Just DM'd you.

Jason: Got it. Throw it in. This is the new config were dropping it in, saving it, I'm reloading the page. 404. David: Why is it 404ing? Did I not save it --

Jason: Check the error payload. It says. Nothing. David: Hold on one second. I'm seeing if I did everything right. One second. I uploaded to sounds. That's what we called it, right?

Jason: Variation, what's this? Decoded. These are the request headers. David: Yeah, I don't -- give it a refresh. Maybe it just needs a second. All right. Go to console -- what is -- I can't even read this.

Jason: Firebase storage, an unknown error occurred. But it's giving us a 404 on the prefix sounds, the limber. It's like I can't load that as the storage name. The bucket name's the same. David: Did I not change the rules? It should be here. Go to the config you pasted in.

Jason: Here, yeah. David: In storage bucket. That should be right -- I don't know. Maybe it's because I literally just created it. That --

Jason: It looked like we were missing a piece. David: Yeah. Go back. App id, okay. One second. That might actually matter.

Jason: We have an app ID in the new one. David: What's the old one?

Jason: Measurement ID? That might just be like a reporting analytics thing. David: Oh, wait a second. Did I not finish booting this up? One second. I may have not clicked all the way through.

Jason: Okay. And if this doesn't work for us, we are unfortunately gonna have to let this one -- David: Yes. I think -- all right. No, I think -- give it one more refresh and cross our fingers and things will just magically work.

Jason: Unfortunately, no. David: Okay.

Jason: Well, chat, you sufficiently caused enough chaos that we actually broke it. Which is fine. We -- so, what we'll do is we're gonna have to -- we'll Tweet about this once we figure out how to get the quota either lifted or -- David: Once the quota goes --

Jason: Is that just a time out? It will clear in an hour or so? David: Yeah, I think it's for the hour or something.

Jason: I'm going to revert here. And yeah. we're getting a quota exceeded. This should work. As soon as the quota clears, we will Tweet about it and see if we can make a bunch of noise at each other's computers. And maybe have a play at the beginning of the next episode to show it work. As always, David, thank you for coming on and hanging out with us today. David: Thank you for having me.

Jason: Got the links in chat. Thank you for spending time today and causing lots of chaos for us. One more shoutout to White Coat Captioning. Thank you so much. Stay tuned. We are going to raid and we will see you next Tuesday!

Closed captioning and more are made possible by our sponsors: