Tag Archives: announcement

Media transcoding and editing, transform and roll out!

Posted by Andrew Lewis - Software Engineer, Android Media Solutions

The creation of user-generated content is on the rise, and users are looking for more ways to personalize and add uniqueness to their creations. These creations are then shared to a vast network of devices, each with its own capabilities. The Jetpack Media3 1.0 release includes new functionality in the Transformer module for converting media files between formats, or transcoding, and applying editing operations. For example, you can trim a clip from a longer piece of media and apply effects to the video track to share over social media, or transcode media into a more efficient codec for upload to a server.

The overall goal of Transformer is to provide an easy to use, reliable and performant API for transcoding and editing media, including support for customizing functionality, following the same API design principles to ExoPlayer. The library is supported on devices running Android 5.0 Lollipop (API 21) onwards and includes device-specific optimizations, giving developers a strong foundation to build on. This post gives an introduction to the new functionality and describes some of the many features we're planning for upcoming releases!


Getting Started

Most operations with Transformer will follow the same general pattern:

  1. Configure a TransformationRequest with settings like your desired output format
  2. Create a Transformer and pass it your TransformationRequest
  3. Apply additional effects and edits
  4. Attach a listener to react to completion events
  5. Start the transformation

Of course, depending on your desired transformations, you may not need every step. Here's an example of transcoding an input video to the H.265/HEVC video format and removing the audio track.

// Create a TransformationRequest and set the output format to H.265 val transformationRequest = TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H265).build() // Create a Transformer val transformer = Transformer.Builder(context) .setTransformationRequest(transformationRequest) // Pass in TransformationRequest .setRemoveAudio(true) // Remove audio track .addListener(transformerListener) // transformerListener is an implementation of Transformer.Listener .build() // Start the transformation val inputMediaItem = MediaItem.fromUri("path_to_input_file") transformer.startTransformation(inputMediaItem, outputPath)

During transformation you can get progress updates with Transformer.getProgress. When the transformation completes the listener is notified in its onTransformationCompleted or onTransformationError callback, and you can process the output media as needed.

Check out our documentation to learn about further capabilities in the Transformer APIs. You can also find details about using Transformer to accurately convert 10-bit HDR content to 8-bit SDR in the "Dealing with color washout" blog post to ensure your video's colors remain as vibrant as possible in the case that your app or the device doesn't support HDR content.


Edits, effects, and extensions

Media3 includes a set of core video effects for simple edits, such as scaling, cropping, and color filters, which you can use with Transformer. For example, you can create a Presentation effect to scale the input to 480p resolution while maintaining the original aspect ratio, and apply it with setVideoEffects:

Transformer.Builder(context) .setVideoEffects(listOf(Presentation.createForHeight(480))) .build()

You can also chain multiple effects to create more complex results. This example converts the input video to grayscale and rotates it by 30 degrees:

Transformer.Builder(context) .setVideoEffects(listOf( RgbFilter.createGrayscaleFilter(), ScaleToFitTransformation.Builder() .setRotationDegrees(30f) .build())) .build()

It's also possible to extend Transformer’s functionality by implementing custom effects that build on existing ones. Here is an example of subclassing MatrixTransformation, where we start zoomed in by 2 times, then zoom out gradually as the frame presentation time increases:

val zoomOutEffect = MatrixTransformation { presentationTimeUs -> val transformationMatrix = Matrix() val scale = 2 - min(1f, presentationTimeUs / 1_000_000f) // Video will zoom from 2x to 1x in the first second transformationMatrix.postScale(/* sx= */ scale, /* sy= */ scale) transformationMatrix // The calculated transformations will be applied each frame in turn } Transformer.Builder(context) .setVideoEffects(listOf(zoomOutEffect)) .build()

Here's a screen recording that shows this effect being applied in the Transformer demo app:

moving image showing what subclassing matrix transformation looks like in the Transformer demo app

For even more advanced use cases, you can wrap your own OpenGL code or other processing libraries in a custom GL texture processor and plug those into Transformer as custom effects. See the demo app for some examples of custom effects. The README also has instructions for trying a demo of MediaPipe integration with Transformer.


Coming soon

Transformer is actively under development but ready to use, so please give it a try and share your feedback! The Media3 development branch includes a sneak peek into several new features building on the 1.0 release described here, including support for tone-mapping HDR videos to SDR using OpenGL, previewing video effects using ExoPlayer.setVideoEffects, and custom audio processing. We are also working on support for editing multiple videos in more flexible compositions, with export from Transformer and playback through ExoPlayer, making Media3 an end-to-end solution for transforming media.

We hope you'll find Transformer an easy-to-use and powerful tool for implementing fantastic media editing experiences on Android! You can send us feature requests and bug reports in the Media3 GitHub issue tracker, and follow this blog to get updates on new features. Stay tuned for our upcoming talk “High quality Android media experiences” at Google I/O.

Become a Google Developer Student Club lead

Posted by Rachel Francois, Program Manager

Do you love programming and Google technologies? Interested in using your tech skills to help your community? Excited to teach other students to code? If the answer to these three questions is yes, we encourage you to apply to be a Lead of Google Developer Student Clubs https://developers.google.com/community/gdsc/leads!

The application for 2023-2024 Leads is OPEN. Get started at goo.gle/gdsc-leads.

Here are a few more details about the program:

What are Google Developer Student Clubs?

Google Developer Student Clubs (GDSC) are university-based community groups for students interested in Google technologies and programming. There are Google Developer Student Clubs in over 110 countries around the world that include undergraduate and graduate students. GDSC members teach each other about Machine Learning, Android app development, Google Cloud, Flutter, and other exciting technologies.

Clubs host events, guest speakers, and hackathons. Many participate in the GDSC Solution Challenge. Students who participate in GDSC have the opportunity to build new skills and learn with their peers and often have the chance to create technical solutions for their communities.

For example, COMSATS Wah University GDSC lead Ahsan Aman built an Android app to detect currency counterfeiting, a problem in his community in Northern Pakistan.

“Through leading my university’s Google Developer Student Club in Islamabad, I came across Machine Learning and other products like Google Teachable Machine,” he says. “With the resources and guidance available from Google, I applied my new skills in across tools like Google Teachable Machine and Android development to build 5Hazar, an application that aims to scan and detect fake currency notes using Machine Learning.”

How will I improve my skills as a GDSC Lead?

As a Google Developer Student Club Lead, you’ll:

  • Receive mentorship from Google.
  • Join a global community of leaders.
  • Share your skills with peers.
  • Teach other students to code and use Google technologies.
  • Build solutions for real-world problems.

Not only will you learn technical skills, you’ll grow your leadership skills, by organizing a student organization and establishing a vision for your community.

“GDSC helped me with personal skills, soft skills, such as public speaking and leadership,” says 2019 GDSC Vancouver Island University lead Lenz Paul. “The highlight was that I learned a lot about Google Cloud technologies, by holding workshops and delivering content.”

How can I find a Google Developer Student Club near me?

There are over 1900 Google Developer Student Clubs in over 110 around the world. Find a club near you here, or learn how to start your own, here.

When do I need to submit the application?

We encourage students to submit your application as soon as possible. Learn more about your region’s application deadlines here, and learn more about GDSC’s program criteria.

Make a difference in your community

From working to solve the United Nations Sustainable Development Goals to combating climate issues, Google Developer Student Club leads are learning valuable coding skills while making a true difference.

“Being a GDSC Lead has brought me tremendous opportunities,” says Rose Niousha, founder of GDSC Waseda University and Women Techmakers ambassador. “Since one of my biggest objectives was to tackle the gender barrier in the tech industry through my GDSC community, I actively hosted events during International Women's Day (IWD) month.”

We look forward to seeing what our next group of Google Developer Student Club Leads can accomplish. Join the fun here.

Note: Google Developer Student Clubs are student-led independent organizations, and their presence does not indicate a relationship between Google and the students' universities.

Media3 is ready to play!

Posted by Nevin Mital - Developer Relations Engineer, Android Media

Today, we’re pleased to announce the full release of the Jetpack Media3 library. After sharing a first look at the library at Android Developer Summit 2021, we published several alpha and beta releases over the past several months to ensure a high-quality set of APIs that we now encourage everyone to adopt.

Media3 is the new home for APIs that enable you to create rich audio and video experiences. If you’ve used libraries like ExoPlayer, MediaCompat, or Media2, you’ll find Media3 to be familiar. However, instead of using these separate libraries, Media3 provides a unified API for playback use-cases and also expands to cover new use-cases like video editing and transcoding. The APIs are simple to use yet powerful, customizable to meet your needs, and reliable and optimized so you can build for the diverse Android device ecosystem.

In this blog post, we’ll focus on the playback APIs in Media3, so please stay tuned for an upcoming post where we’ll dive deeper into the video editing and transcoding APIs. As a brief introduction, the following table describes key components for playback in Media3:

Player

An interface that defines traditional high-level functionality for an audio or video player, such as playback controls.

ExoPlayer

The default implementation of the Player interface in Media3.

MediaSession

An API that advertises media playback to and receives playback command requests from external clients.

MediaSessionService

A service that holds a MediaSession to enable background playback.

MediaLibraryService

A service that additionally allows you to expose a content library to external clients.

MediaController

An API that is generally used by external clients to retrieve playback information and send playback command requests to your media app. Complementary to a MediaSession. Examples of external clients include the notification and lock screen media controls on mobile and large screen devices, Android Auto, WearOS, and Google Assistant.

MediaBrowser

An API that additionally enables external clients to navigate your media app’s content library. Complementary to a MediaLibraryService.

Our developer documentation has more details on these components. Let’s take a closer look into what this new library offers and how you can start using it.

Keeping it simple

By consolidating the APIs for the playback developer journey into a single library, Media3 is able to introduce a Player interface that is used by several components, such as MediaSession and MediaController. This interface outlines traditional high-level functionality for audio and video playback, such as playback controls and the ability to query properties of the currently playing media.

Having a common interface for all “player-like” components means that creating new instances of these objects is straightforward:

val player = ExoPlayer.builder(context).build() val session = MediaSession.Builder(context, player).build() val controller = MediaController.Builder(context, session.token).build()

Media3's MediaSession and MediaController will automatically reflect the state of the components they're connected to. As a result, you can also simplify your app’s architecture by removing connectors like ExoPlayer’s MediaSessionConnector and more easily follow the flow of logic through your app. Calling play() on the MediaController will forward the action to the MediaSession, which will then forward it to the player.

Similarly, Media3 aims to make background playback cases easier to handle. The PlayerNotificationManager from ExoPlayer is no longer needed, as Media3’s MediaSessionService and MediaLibraryService automatically handle publishing a media notification as needed. The library handles configuring, starting, and stopping a foreground service for you as needed, but please also note some known issues summarized in this comment.

ExoPlayer is deprecated, long live ExoPlayer!

ExoPlayer has a new home and is the default implementation of the aforementioned Player interface in Media3. The standalone ExoPlayer project, with package name com.google.android.exoplayer2, will soon be discontinued, and future updates will be published in Media3. For the next few months, we’ll continue publishing equivalent releases of both Media3 and ExoPlayer to help you make the transition to Media3. For example, this means that ExoPlayer 2.18.5 and ExoPlayer in Media3 1.0.0 are identical aside from their package names. However, this is only temporary and we will deprecate the standalone ExoPlayer later this year, so we highly recommend migrating to Media3 as soon as possible. The “Migrating to Media3” section below describes the process in more detail, which includes a script that handles most of the work for you.

Note that Media3 is developed with the same philosophy as ExoPlayer (and in fact, is developed by the same team!). In other words, Media3 retains ExoPlayer’s customizable components, open source development on GitHub, receptivity to pull requests, and public issue tracker, to name a few similarities.

Migrating to Media3

As mentioned previously, the standalone ExoPlayer project, with package name com.google.android.exoplayer2, will soon be discontinued, so to continue receiving updates, you will need to migrate to Media3 ExoPlayer. Other Media APIs that should be migrated to Media3 include, but are not limited to, MediaSessionConnectorMediaBrowserServiceCompat, and MediaBrowserCompat.

We’ve prepared two key resources to help you achieve this migration as smoothly as possible:

  1. migration guide to walk you through the process step-by-step
  2. migration script to convert your standalone ExoPlayer project packages to the corresponding new modules and packages under Media3

The good news is that if you’re currently using ExoPlayer, there’s no need for any code changes and no need to re-integrate or re-write any customizations. The standalone ExoPlayer and Media3 ExoPlayer are identical aside from the package name, and the conversion can be done automatically with the aforementioned migration script. Just make sure you’ve updated your project to use the latest version of ExoPlayer before getting started. For full details and steps, please refer to the migration guide.

Furthermore, since Media3 is fully backwards-compatible with prior media APIs such as MediaControllerCompat and MediaMetadataCompat, your existing integrations will continue to work as before even after the migration. Note that new features such as per-controller customization of commands are only available for clients using Media3. That is to say, for example, all legacy controllers, such as MediaControllerCompat, will receive the same set of available commands. You can identify a legacy controller by checking if getControllerVersion() returns 0 in the MediaSession.ControllerInfo.

The power of Media3, in the palm of your hand

Media3 offers several options for you to adjust its behavior to better fit your needs. The next few sections describe some such mechanisms.

Play it your own way

Although ExoPlayer is the recommended Player implementation to use for audio and video streaming apps, Media3 also introduces the SimpleBasePlayer to minimize the number of methods you need to implement to integrate with a custom player. Start by implementing the getState method. This is where you can declare the Command set supported by your player and configure metadata such as the currently playing media item index and the current timestamp.

class CustomPlayer : SimpleBasePlayer(looper) { override fun getState(): State { // Set available Commands // Configure playWhenReady, mediaItemIndex, currentPosition, etc. } // Implement methods required by available Commands }

The SimpleBasePlayer class will enforce valid player state and handle informing listeners of state changes. Additionally, any methods related to a Command you don’t declare as available are ignored, so beyond getState, you only need to implement the methods that will actually be used.

Better control over your commands

The MediaSession and MediaController APIs have also been updated to give you more control. With Media3, you can advertise your app’s playback capabilities on a per-controller basis. Modify the commands available to a client app in the onConnect method of your MediaSession.Callback. For example, to prevent a client app with package name com.example.myClient from having access to the “seek to next media item” Player.Command:

var sessionCallback = object : MediaSession.Callback { override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): MediaSession.ConnectionResult { val connectionResult = super.onConnect(session, controller) if (controller.packageName == "com.example.myClient") { val availablePlayerCommands = connectionResult.availablePlayerCommands.buildUpon() .remove(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) // Disallow myClient from being able to skip to the next media item .build() return MediaSession.ConnectionResult.accept( connectionResult.availableSessionCommands, availablePlayerCommands ) } return connectionResult // Other clients retain normal command access } } var mediaSession = MediaSession.Builder(context, player) .setCallback(sessionCallback) // Remember to set the callback on your MediaSession! .build()

Creating custom commands

Of course, as with the previous media APIs, you can add custom commands tailored to your app. To implement a custom command, create a new SessionCommand. Similar to as shown above, you can give controllers access to this custom command by including it in the list of available session commands. You can handle custom command behavior in the onCustomCommand method of the same Callback:

override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture<SessionResult> { if (customCommand.customAction == MY_CUSTOM_COMMAND) { // Do custom action return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } // Return error for invalid custom command return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_BAD_VALUE)) }

You can also ask client apps to display your custom command by including it in a setCustomLayout call in the onPostConnect method of the MediaSession.Callback.

Next steps

We’d love for you to start using Media3 in your app! 

To start exploring the library, feel free to check out the demo app to see an example of audio and video playback, including how to integrate with a media session. Stay tuned to our developer guides for more detailed guidance on the different components in Media3 landing soon. Our sample app, the Universal Android Music Player, and our testing tool, the Media Controller Test app, will also be updated to Media3 on their main branches in the coming weeks.

If you run into any issues, have any feature requests, or would like to share any other sort of feedback, please let us know using the Media3 issue tracker on GitHub. We look forward to hearing from you!

Introducing the Google for Startups Accelerator: Southeast Asia 2022 cohort

Posted by Thye Yeow Bok, Head of Startup Ecosystem, SEA, SAF and Greater China Region

As Southeast Asia recovers from the pandemic, analysts predict that the region's digital economy will increase 20% YoY to reach just shy of $200B in gross merchandise value in 2022 — three years ahead of predictions about the region's growth in 2016. New digital services emerged during the last few years, such as telemedicine, digital payments, and online education platforms. We also saw innovation in legacy sectors such as agriculture, logistics, talent retention, and recruitment due to the adoption of advanced technology such as cloud, AI, and machine learning.

While the region isn't immune to the macroeconomic headwind, companies like Grab and Gojek have placed Southeast Asia on the global startup map with high investment potential.

In October 2022, to help support this growing startup ecosystem, Google announced an open call for applications to its 7th Google for Startups Accelerator: Southeast Asia program. This three-month hybrid program supports tech startups building innovative solutions to cater to advancing digital transformation across Southeast Asia.

After reviewing all applications, we're delighted to announce the ten phenomenal startups selected to attend the program:

  • Bluente (Singapore, Education): The world's first business language learning mobile app, delivering bite-sized and personalized courses for working professionals.
  • Docosan (Vietnam, Healthcare): A healthtech marketplace expanding access to healthcare via online and offline bookings with providers and direct-to-consumer, digitally enabled testing and treatment journeys.
  • Elfie (Singapore, Healthcare): A product that reduces the burden of chronic diseases by rewarding patients for self-monitoring.
  • HD (Thailand, Healthcare) - The largest healthcare and surgery marketplace in emerging Southeast Asia, described as theBooking.com for health and Airbnb for surgeries.
  • Mindtera (Indonesia, Human Resources): An award-winning platform for Employee Assistance Programs (EAP) that uses data-driven insights to increase performance, productivity, and well-being.
  • NOICE (Indonesia, Content/Media): Indonesia's #1 local audio platform that serves multi-vertical content from podcasts, audio series, live audio, and radio streaming.
  • OOOLAB (Vietnam, Education): An edtech startup that powers the world's most delightful learning experiences.
  • PasarMIKRO (Indonesia, Agriculture): Indonesia's first agriculture commodity trade platform for smallholder farmers.
  • Rider (Pakistan, Logistics) A modern logistics provider for online sellers in Pakistan, working with 750 online sellers across 60 cities.
  • Wela School System (Philippines, Education): An end-to-end school system (software) where students and parents can view their grades, announcements, and billing statements through a mobile app.

Through mentoring, consultation, and training on technology, product management, business development, marketing, and leadership, we hope to bring the best of Google's resources, experts, products, and network to support all these startups in their entrepreneurial journey.

Stricter field validation implemented for Doubleclick Bid Manager API

Beginning in June 2020, we rolled out stricter validation of long and integer request fields in the Doubleclick Bid Manager (DBM) API. This rollout finished in early August and now applies to all traffic.

This new request validation no longer allows the use of decimals in string values submitted for long and integer field types. Previously, an integer field in a request body would accept, for example, “123.0” as a permissible value. Now, identical requests will return an HTTP 400 error with status INVALID_ARGUMENT.

Before the implementation of this validation, the API truncated invalid values at the decimal point and the digits after it were ignored. You can replicate this previous behavior by truncating values at the decimal place before making a request.

Verify that your code converts values with decimals to longs and integers. If you receive an INVALID_ARGUMENT error, make sure your numbers are actual longs or integers.

If you need help adjusting for this new validation or want to report a separate issue, please contact us using our support contact form.

Announcing v3.4 of the DCM/DFA Reporting and Trafficking API

Today we're releasing v3.4 of the DCM/DFA Reporting and Trafficking API. Highlights of this release include:

Details of these and other changes are covered in our release notes.

Deprecation and sunset reminder

In accordance with our deprecation schedule, this release marks the beginning of the deprecation period for v3.3, which will sunset on February 28, 2021. After this date, any requests made against v3.3 will begin returning errors.

Learn More

As with every new version of the DCM/DFA Reporting and Trafficking API, we encourage you to carefully review all changes in the release notes. For those of you looking to get going right away, updated client libraries are now available. If you're just starting out, the Get Started guide is a great reference to help you get up and running quickly.


Give it a try and let us know if you have any questions!

Stricter Validation For DCM/DFA Reporting and Trafficking API

On July 14th, 2020, we will begin enforcing stricter validation for requests to the DCM/DFA Reporting and Trafficking API. This change will initially be introduced for 5% of API traffic during the week of July 14, 10% the week of July 21, 30% the week of July 28, and all requests by August 7th, 2020
As a result, your requests might begin returning additional errors. Please update your requests, as needed. 

The following list includes the error codes that you might see, as well as the recommended solutions:
  • "Invalid value - Enum" - Ensure you are using valid enum values. For more information, see the API Reference.
  • "Invalid value - Bool" - Ensure boolean fields in the request are set to true/false, rather than empty string.
  • "Invalid value - Integer ####.0" - Ensure integer fields in the request are set to valid integers.
  • "Invalid JSON Payload - NaN" - Ensure NaN does not appear in the request payload.
  • "Invalid JSON payload received. - not repeating" - Do not use arrays for non-array fields.
Method specific errors:
  • dfareporting.reports.run: "Invalid JSON payload received. Unexpected end of string. Expected a value" - Ensure the request body is completely empty.
  • dfareporting.reports.list: "Request contains an invalid argument." - Ensure that empty fields in the response contain a valid value. For example, empty array fields must be specified by an empty array, rather than an empty string or object.
  • dfareporting.files.get and dfareporting.reports.files.get: "OAuth token was passed in the query parameter. Please send it in Authorization header instead." - Requests must use the HTTP header Authorization: Bearer [ACCESS_TOKEN] to pass OAuth credentials.

Why is this changing?
Stricter validation helps ensure that problematic requests are not silently ignored, and aligns the behavior of the DCM API with that of other Google APIs.

Stricter Validation For DCM/DFA Reporting and Trafficking API

On July 14th, 2020, we will begin enforcing stricter validation for requests to the DCM/DFA Reporting and Trafficking API. This change will initially be introduced for 5% of API traffic during the week of July 14, 10% the week of July 21, 30% the week of July 28, and all requests by August 7th, 2020
As a result, your requests might begin returning additional errors. Please update your requests, as needed. 

The following list includes the error codes that you might see, as well as the recommended solutions:
  • "Invalid value - Enum" - Ensure you are using valid enum values. For more information, see the API Reference.
  • "Invalid value - Bool" - Ensure boolean fields in the request are set to true/false, rather than empty string.
  • "Invalid value - Integer ####.0" - Ensure integer fields in the request are set to valid integers.
  • "Invalid JSON Payload - NaN" - Ensure NaN does not appear in the request payload.
  • "Invalid JSON payload received. - not repeating" - Do not use arrays for non-array fields.
Method specific errors:
  • dfareporting.reports.run: "Invalid JSON payload received. Unexpected end of string. Expected a value" - Ensure the request body is completely empty.
  • dfareporting.reports.list: "Request contains an invalid argument." - Ensure that empty fields in the response contain a valid value. For example, empty array fields must be specified by an empty array, rather than an empty string or object.
  • dfareporting.files.get and dfareporting.reports.files.get: "OAuth token was passed in the query parameter. Please send it in Authorization header instead." - Requests must use the HTTP header Authorization: Bearer [ACCESS_TOKEN] to pass OAuth credentials.

Why is this changing?
Stricter validation helps ensure that problematic requests are not silently ignored, and aligns the behavior of the DCM API with that of other Google APIs.

Update on Google at GDC 2020

Posted by the Google for Games Team

Last Friday, GDC 2020 organizers made the difficult decision to postpone the conference. We understand this decision, as we have to prioritize the health and safety of our community.

Every year, we look forward to the Game Developers Conference and surrounding events because it gives our teams a chance to connect with game developers, partners, and friends in the industry.

Although we won’t be connecting in-person this year, we’re still excited to share the latest announcements from Google with everyone through our digital experience. We'll be sharing plans for our digital experience in the coming days.

Thank you to all who keep this community thriving and check back soon at g.co/gdc2020 for more details.

Update on Google at GDC 2020

Posted by the Google for Games Team

Last Friday, GDC 2020 organizers made the difficult decision to postpone the conference. We understand this decision, as we have to prioritize the health and safety of our community.

Every year, we look forward to the Game Developers Conference and surrounding events because it gives our teams a chance to connect with game developers, partners, and friends in the industry.

Although we won’t be connecting in-person this year, we’re still excited to share the latest announcements from Google with everyone through our digital experience. We'll be sharing plans for our digital experience in the coming days.

Thank you to all who keep this community thriving and check back soon at g.co/gdc2020 for more details.