1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media; 18 19 import android.annotation.NonNull; 20 import android.content.ContentResolver; 21 import android.net.Uri; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.util.Log; 27 28 import com.android.modules.annotation.MinSdk; 29 30 import org.xmlpull.v1.XmlPullParser; 31 import org.xmlpull.v1.XmlPullParserException; 32 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 40 /** 41 ApplicationMediaCapabilities is an immutable class that encapsulates an application's capabilities 42 for handling newer video codec format and media features. 43 44 <p> 45 Android 12 introduces Compatible media transcoding feature. See 46 <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding"> 47 Compatible media transcoding</a>. By default, Android assumes apps can support playback of all 48 media formats. Apps that would like to request that media be transcoded into a more compatible 49 format should declare their media capabilities in a media_capabilities.xml resource file and add it 50 as a property tag in the AndroidManifest.xml file. Here is a example: 51 <pre> 52 {@code 53 <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> 54 <format android:name="HEVC" supported="true"/> 55 <format android:name="HDR10" supported="false"/> 56 <format android:name="HDR10Plus" supported="false"/> 57 </media-capabilities> 58 } 59 </pre> 60 The ApplicationMediaCapabilities class is generated from this xml and used by the platform to 61 represent an application's media capabilities in order to determine whether modern media files need 62 to be transcoded for that application. 63 </p> 64 65 <p> 66 ApplicationMediaCapabilities objects can also be built by applications at runtime for use with 67 {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more 68 control over the transcoding that is built into the platform. ApplicationMediaCapabilities 69 provided by applications at runtime like this override the default manifest capabilities for that 70 media access.The object could be build either through {@link #createFromXml(XmlPullParser)} or 71 through the builder class {@link ApplicationMediaCapabilities.Builder} 72 73 <h3> Video Codec Support</h3> 74 <p> 75 Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support 76 for newer format with this class as they are assumed to support older format like h.264. 77 78 <h3>Capability of handling HDR(high dynamic range) video</h3> 79 <p> 80 There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform, 81 application will only need to specify individual types they supported. 82 */ 83 @MinSdk(Build.VERSION_CODES.S) 84 public final class ApplicationMediaCapabilities implements Parcelable { 85 private static final String TAG = "ApplicationMediaCapabilities"; 86 87 /** List of supported video codec mime types. */ 88 private Set<String> mSupportedVideoMimeTypes = new HashSet<>(); 89 90 /** List of unsupported video codec mime types. */ 91 private Set<String> mUnsupportedVideoMimeTypes = new HashSet<>(); 92 93 /** List of supported hdr types. */ 94 private Set<String> mSupportedHdrTypes = new HashSet<>(); 95 96 /** List of unsupported hdr types. */ 97 private Set<String> mUnsupportedHdrTypes = new HashSet<>(); 98 99 private boolean mIsSlowMotionSupported = false; 100 ApplicationMediaCapabilities(Builder b)101 private ApplicationMediaCapabilities(Builder b) { 102 mSupportedVideoMimeTypes.addAll(b.getSupportedVideoMimeTypes()); 103 mUnsupportedVideoMimeTypes.addAll(b.getUnsupportedVideoMimeTypes()); 104 mSupportedHdrTypes.addAll(b.getSupportedHdrTypes()); 105 mUnsupportedHdrTypes.addAll(b.getUnsupportedHdrTypes()); 106 mIsSlowMotionSupported = b.mIsSlowMotionSupported; 107 } 108 109 /** 110 * Query if a video codec format is supported by the application. 111 * <p> 112 * If the application has not specified supporting the format or not, this will return false. 113 * Use {@link #isFormatSpecified(String)} to query if a format is specified or not. 114 * 115 * @param videoMime The mime type of the video codec format. Must be the one used in 116 * {@link MediaFormat#KEY_MIME}. 117 * @return true if application supports the video codec format, false otherwise. 118 */ isVideoMimeTypeSupported( @onNull String videoMime)119 public boolean isVideoMimeTypeSupported( 120 @NonNull String videoMime) { 121 if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { 122 return true; 123 } 124 return false; 125 } 126 127 /** 128 * Query if a HDR type is supported by the application. 129 * <p> 130 * If the application has not specified supporting the format or not, this will return false. 131 * Use {@link #isFormatSpecified(String)} to query if a format is specified or not. 132 * 133 * @param hdrType The type of the HDR format. 134 * @return true if application supports the HDR format, false otherwise. 135 */ isHdrTypeSupported( @onNull @ediaFeature.MediaHdrType String hdrType)136 public boolean isHdrTypeSupported( 137 @NonNull @MediaFeature.MediaHdrType String hdrType) { 138 if (mSupportedHdrTypes.contains(hdrType)) { 139 return true; 140 } 141 return false; 142 } 143 144 /** 145 * Query if a format is specified by the application. 146 * <p> 147 * The format could be either the video format or the hdr format. 148 * 149 * @param format The name of the format. 150 * @return true if application specifies the format, false otherwise. 151 */ isFormatSpecified(@onNull String format)152 public boolean isFormatSpecified(@NonNull String format) { 153 if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format) 154 || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) { 155 return true; 156 157 } 158 return false; 159 } 160 161 @Override describeContents()162 public int describeContents() { 163 return 0; 164 } 165 166 @Override writeToParcel(@onNull Parcel dest, int flags)167 public void writeToParcel(@NonNull Parcel dest, int flags) { 168 // Write out the supported video mime types. 169 dest.writeInt(mSupportedVideoMimeTypes.size()); 170 for (String cap : mSupportedVideoMimeTypes) { 171 dest.writeString(cap); 172 } 173 // Write out the unsupported video mime types. 174 dest.writeInt(mUnsupportedVideoMimeTypes.size()); 175 for (String cap : mUnsupportedVideoMimeTypes) { 176 dest.writeString(cap); 177 } 178 // Write out the supported hdr types. 179 dest.writeInt(mSupportedHdrTypes.size()); 180 for (String cap : mSupportedHdrTypes) { 181 dest.writeString(cap); 182 } 183 // Write out the unsupported hdr types. 184 dest.writeInt(mUnsupportedHdrTypes.size()); 185 for (String cap : mUnsupportedHdrTypes) { 186 dest.writeString(cap); 187 } 188 // Write out the supported slow motion. 189 dest.writeBoolean(mIsSlowMotionSupported); 190 } 191 192 @Override toString()193 public String toString() { 194 String caps = new String( 195 "Supported Video MimeTypes: " + mSupportedVideoMimeTypes.toString()); 196 caps += "Unsupported Video MimeTypes: " + mUnsupportedVideoMimeTypes.toString(); 197 caps += "Supported HDR types: " + mSupportedHdrTypes.toString(); 198 caps += "Unsupported HDR types: " + mUnsupportedHdrTypes.toString(); 199 caps += "Supported slow motion: " + mIsSlowMotionSupported; 200 return caps; 201 } 202 203 @NonNull 204 public static final Creator<ApplicationMediaCapabilities> CREATOR = 205 new Creator<ApplicationMediaCapabilities>() { 206 public ApplicationMediaCapabilities createFromParcel(Parcel in) { 207 ApplicationMediaCapabilities.Builder builder = 208 new ApplicationMediaCapabilities.Builder(); 209 210 // Parse supported video codec mime types. 211 int count = in.readInt(); 212 for (int readCount = 0; readCount < count; ++readCount) { 213 builder.addSupportedVideoMimeType(in.readString()); 214 } 215 216 // Parse unsupported video codec mime types. 217 count = in.readInt(); 218 for (int readCount = 0; readCount < count; ++readCount) { 219 builder.addUnsupportedVideoMimeType(in.readString()); 220 } 221 222 // Parse supported hdr types. 223 count = in.readInt(); 224 for (int readCount = 0; readCount < count; ++readCount) { 225 builder.addSupportedHdrType(in.readString()); 226 } 227 228 // Parse unsupported hdr types. 229 count = in.readInt(); 230 for (int readCount = 0; readCount < count; ++readCount) { 231 builder.addUnsupportedHdrType(in.readString()); 232 } 233 234 boolean supported = in.readBoolean(); 235 builder.setSlowMotionSupported(supported); 236 237 return builder.build(); 238 } 239 240 public ApplicationMediaCapabilities[] newArray(int size) { 241 return new ApplicationMediaCapabilities[size]; 242 } 243 }; 244 245 /** 246 * Query the video codec mime types supported by the application. 247 * @return List of supported video codec mime types. The list will be empty if there are none. 248 */ 249 @NonNull getSupportedVideoMimeTypes()250 public List<String> getSupportedVideoMimeTypes() { 251 return new ArrayList<>(mSupportedVideoMimeTypes); 252 } 253 254 /** 255 * Query the video codec mime types that are not supported by the application. 256 * @return List of unsupported video codec mime types. The list will be empty if there are none. 257 */ 258 @NonNull getUnsupportedVideoMimeTypes()259 public List<String> getUnsupportedVideoMimeTypes() { 260 return new ArrayList<>(mUnsupportedVideoMimeTypes); 261 } 262 263 /** 264 * Query all hdr types that are supported by the application. 265 * @return List of supported hdr types. The list will be empty if there are none. 266 */ 267 @NonNull getSupportedHdrTypes()268 public List<String> getSupportedHdrTypes() { 269 return new ArrayList<>(mSupportedHdrTypes); 270 } 271 272 /** 273 * Query all hdr types that are not supported by the application. 274 * @return List of unsupported hdr types. The list will be empty if there are none. 275 */ 276 @NonNull getUnsupportedHdrTypes()277 public List<String> getUnsupportedHdrTypes() { 278 return new ArrayList<>(mUnsupportedHdrTypes); 279 } 280 281 /** 282 * Whether handling of slow-motion video is supported 283 * @hide 284 */ isSlowMotionSupported()285 public boolean isSlowMotionSupported() { 286 return mIsSlowMotionSupported; 287 } 288 289 /** 290 * Creates {@link ApplicationMediaCapabilities} from an xml. 291 * 292 * The xml's syntax is the same as the media_capabilities.xml used by the AndroidManifest.xml. 293 * <p> Here is an example: 294 * 295 * <pre> 296 * {@code 297 * <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> 298 * <format android:name="HEVC" supported="true"/> 299 * <format android:name="HDR10" supported="false"/> 300 * <format android:name="HDR10Plus" supported="false"/> 301 * </media-capabilities> 302 * } 303 * </pre> 304 * <p> 305 * 306 * @param xmlParser The underlying {@link XmlPullParser} that will read the xml. 307 * @return An ApplicationMediaCapabilities object. 308 * @throws UnsupportedOperationException if the capabilities in xml config are invalid or 309 * incompatible. 310 */ 311 // TODO: Add developer.android.com link for the format of the xml. 312 @NonNull createFromXml(@onNull XmlPullParser xmlParser)313 public static ApplicationMediaCapabilities createFromXml(@NonNull XmlPullParser xmlParser) { 314 ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder(); 315 builder.parseXml(xmlParser); 316 return builder.build(); 317 } 318 319 /** 320 * Builder class for {@link ApplicationMediaCapabilities} objects. 321 * Use this class to configure and create an ApplicationMediaCapabilities instance. Builder 322 * could be created from an existing ApplicationMediaCapabilities object, from a xml file or 323 * MediaCodecList. 324 * //TODO(hkuang): Add xml parsing support to the builder. 325 */ 326 public final static class Builder { 327 /** List of supported video codec mime types. */ 328 private Set<String> mSupportedVideoMimeTypes = new HashSet<>(); 329 330 /** List of supported hdr types. */ 331 private Set<String> mSupportedHdrTypes = new HashSet<>(); 332 333 /** List of unsupported video codec mime types. */ 334 private Set<String> mUnsupportedVideoMimeTypes = new HashSet<>(); 335 336 /** List of unsupported hdr types. */ 337 private Set<String> mUnsupportedHdrTypes = new HashSet<>(); 338 339 private boolean mIsSlowMotionSupported = false; 340 341 /* Map to save the format read from the xml. */ 342 private Map<String, Boolean> mFormatSupportedMap = new HashMap<String, Boolean>(); 343 344 /** 345 * Constructs a new Builder with all the supports default to false. 346 */ Builder()347 public Builder() { 348 } 349 parseXml(@onNull XmlPullParser xmlParser)350 private void parseXml(@NonNull XmlPullParser xmlParser) 351 throws UnsupportedOperationException { 352 if (xmlParser == null) { 353 throw new IllegalArgumentException("XmlParser must not be null"); 354 } 355 356 try { 357 while (xmlParser.next() != XmlPullParser.START_TAG) { 358 continue; 359 } 360 361 // Validates the tag is "media-capabilities". 362 if (!xmlParser.getName().equals("media-capabilities")) { 363 throw new UnsupportedOperationException("Invalid tag"); 364 } 365 366 xmlParser.next(); 367 while (xmlParser.getEventType() != XmlPullParser.END_TAG) { 368 while (xmlParser.getEventType() != XmlPullParser.START_TAG) { 369 if (xmlParser.getEventType() == XmlPullParser.END_DOCUMENT) { 370 return; 371 } 372 xmlParser.next(); 373 } 374 375 // Validates the tag is "format". 376 if (xmlParser.getName().equals("format")) { 377 parseFormatTag(xmlParser); 378 } else { 379 throw new UnsupportedOperationException("Invalid tag"); 380 } 381 while (xmlParser.getEventType() != XmlPullParser.END_TAG) { 382 xmlParser.next(); 383 } 384 xmlParser.next(); 385 } 386 } catch (XmlPullParserException xppe) { 387 throw new UnsupportedOperationException("Ill-formatted xml file"); 388 } catch (java.io.IOException ioe) { 389 throw new UnsupportedOperationException("Unable to read xml file"); 390 } 391 } 392 parseFormatTag(XmlPullParser xmlParser)393 private void parseFormatTag(XmlPullParser xmlParser) { 394 String name = null; 395 String supported = null; 396 for (int i = 0; i < xmlParser.getAttributeCount(); i++) { 397 String attrName = xmlParser.getAttributeName(i); 398 if (attrName.equals("name")) { 399 name = xmlParser.getAttributeValue(i); 400 } else if (attrName.equals("supported")) { 401 supported = xmlParser.getAttributeValue(i); 402 } else { 403 throw new UnsupportedOperationException("Invalid attribute name " + attrName); 404 } 405 } 406 407 if (name != null && supported != null) { 408 if (!supported.equals("true") && !supported.equals("false")) { 409 throw new UnsupportedOperationException( 410 ("Supported value must be either true or false")); 411 } 412 boolean isSupported = Boolean.parseBoolean(supported); 413 414 // Check if the format is already found before. 415 if (mFormatSupportedMap.get(name) != null && mFormatSupportedMap.get(name) 416 != isSupported) { 417 throw new UnsupportedOperationException( 418 "Format: " + name + " has conflict supported value"); 419 } 420 421 switch (name) { 422 case "HEVC": 423 if (isSupported) { 424 mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_HEVC); 425 } else { 426 mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_HEVC); 427 } 428 break; 429 case "VP9": 430 if (isSupported) { 431 mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_VP9); 432 } else { 433 mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_VP9); 434 } 435 break; 436 case "AV1": 437 if (isSupported) { 438 mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_AV1); 439 } else { 440 mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_AV1); 441 } 442 break; 443 case "HDR10": 444 if (isSupported) { 445 mSupportedHdrTypes.add(MediaFeature.HdrType.HDR10); 446 } else { 447 mUnsupportedHdrTypes.add(MediaFeature.HdrType.HDR10); 448 } 449 break; 450 case "HDR10Plus": 451 if (isSupported) { 452 mSupportedHdrTypes.add(MediaFeature.HdrType.HDR10_PLUS); 453 } else { 454 mUnsupportedHdrTypes.add(MediaFeature.HdrType.HDR10_PLUS); 455 } 456 break; 457 case "Dolby-Vision": 458 if (isSupported) { 459 mSupportedHdrTypes.add(MediaFeature.HdrType.DOLBY_VISION); 460 } else { 461 mUnsupportedHdrTypes.add(MediaFeature.HdrType.DOLBY_VISION); 462 } 463 break; 464 case "HLG": 465 if (isSupported) { 466 mSupportedHdrTypes.add(MediaFeature.HdrType.HLG); 467 } else { 468 mUnsupportedHdrTypes.add(MediaFeature.HdrType.HLG); 469 } 470 break; 471 case "SlowMotion": 472 mIsSlowMotionSupported = isSupported; 473 break; 474 default: 475 Log.w(TAG, "Invalid format name " + name); 476 } 477 // Save the name and isSupported into the map for validate later. 478 mFormatSupportedMap.put(name, isSupported); 479 } else { 480 throw new UnsupportedOperationException( 481 "Format name and supported must both be specified"); 482 } 483 } 484 485 /** 486 * Builds a {@link ApplicationMediaCapabilities} object. 487 * 488 * @return a new {@link ApplicationMediaCapabilities} instance successfully initialized 489 * with all the parameters set on this <code>Builder</code>. 490 * @throws UnsupportedOperationException if the parameters set on the 491 * <code>Builder</code> were incompatible, or if they 492 * are not supported by the 493 * device. 494 */ 495 @NonNull build()496 public ApplicationMediaCapabilities build() { 497 Log.d(TAG, 498 "Building ApplicationMediaCapabilities with: (Supported HDR: " 499 + mSupportedHdrTypes.toString() + " Unsupported HDR: " 500 + mUnsupportedHdrTypes.toString() + ") (Supported Codec: " 501 + " " + mSupportedVideoMimeTypes.toString() + " Unsupported Codec:" 502 + mUnsupportedVideoMimeTypes.toString() + ") " 503 + mIsSlowMotionSupported); 504 505 // If hdr is supported, application must also support hevc. 506 if (!mSupportedHdrTypes.isEmpty() && !mSupportedVideoMimeTypes.contains( 507 MediaFormat.MIMETYPE_VIDEO_HEVC)) { 508 throw new UnsupportedOperationException("Only support HEVC mime type"); 509 } 510 return new ApplicationMediaCapabilities(this); 511 } 512 513 /** 514 * Adds a supported video codec mime type. 515 * 516 * @param codecMime Supported codec mime types. Must be one of the mime type defined 517 * in {@link MediaFormat}. 518 * @throws IllegalArgumentException if mime type is not valid. 519 */ 520 @NonNull addSupportedVideoMimeType( @onNull String codecMime)521 public Builder addSupportedVideoMimeType( 522 @NonNull String codecMime) { 523 mSupportedVideoMimeTypes.add(codecMime); 524 return this; 525 } 526 getSupportedVideoMimeTypes()527 private List<String> getSupportedVideoMimeTypes() { 528 return new ArrayList<>(mSupportedVideoMimeTypes); 529 } 530 isValidVideoCodecMimeType(@onNull String codecMime)531 private boolean isValidVideoCodecMimeType(@NonNull String codecMime) { 532 if (!codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC) 533 && !codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9) 534 && !codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) { 535 return false; 536 } 537 return true; 538 } 539 540 /** 541 * Adds an unsupported video codec mime type. 542 * 543 * @param codecMime Unsupported codec mime type. Must be one of the mime type defined 544 * in {@link MediaFormat}. 545 * @throws IllegalArgumentException if mime type is not valid. 546 */ 547 @NonNull addUnsupportedVideoMimeType( @onNull String codecMime)548 public Builder addUnsupportedVideoMimeType( 549 @NonNull String codecMime) { 550 if (!isValidVideoCodecMimeType(codecMime)) { 551 throw new IllegalArgumentException("Invalid codec mime type: " + codecMime); 552 } 553 mUnsupportedVideoMimeTypes.add(codecMime); 554 return this; 555 } 556 getUnsupportedVideoMimeTypes()557 private List<String> getUnsupportedVideoMimeTypes() { 558 return new ArrayList<>(mUnsupportedVideoMimeTypes); 559 } 560 561 /** 562 * Adds a supported hdr type. 563 * 564 * @param hdrType Supported hdr type. Must be one of the String defined in 565 * {@link MediaFeature.HdrType}. 566 * @throws IllegalArgumentException if hdrType is not valid. 567 */ 568 @NonNull addSupportedHdrType( @onNull @ediaFeature.MediaHdrType String hdrType)569 public Builder addSupportedHdrType( 570 @NonNull @MediaFeature.MediaHdrType String hdrType) { 571 if (!isValidVideoCodecHdrType(hdrType)) { 572 throw new IllegalArgumentException("Invalid hdr type: " + hdrType); 573 } 574 mSupportedHdrTypes.add(hdrType); 575 return this; 576 } 577 getSupportedHdrTypes()578 private List<String> getSupportedHdrTypes() { 579 return new ArrayList<>(mSupportedHdrTypes); 580 } 581 isValidVideoCodecHdrType(@onNull String hdrType)582 private boolean isValidVideoCodecHdrType(@NonNull String hdrType) { 583 if (!hdrType.equals(MediaFeature.HdrType.DOLBY_VISION) 584 && !hdrType.equals(MediaFeature.HdrType.HDR10) 585 && !hdrType.equals(MediaFeature.HdrType.HDR10_PLUS) 586 && !hdrType.equals(MediaFeature.HdrType.HLG)) { 587 return false; 588 } 589 return true; 590 } 591 592 /** 593 * Adds an unsupported hdr type. 594 * 595 * @param hdrType Unsupported hdr type. Must be one of the String defined in 596 * {@link MediaFeature.HdrType}. 597 * @throws IllegalArgumentException if hdrType is not valid. 598 */ 599 @NonNull addUnsupportedHdrType( @onNull @ediaFeature.MediaHdrType String hdrType)600 public Builder addUnsupportedHdrType( 601 @NonNull @MediaFeature.MediaHdrType String hdrType) { 602 if (!isValidVideoCodecHdrType(hdrType)) { 603 throw new IllegalArgumentException("Invalid hdr type: " + hdrType); 604 } 605 mUnsupportedHdrTypes.add(hdrType); 606 return this; 607 } 608 getUnsupportedHdrTypes()609 private List<String> getUnsupportedHdrTypes() { 610 return new ArrayList<>(mUnsupportedHdrTypes); 611 } 612 613 /** 614 * Sets whether slow-motion video is supported. 615 * If an application indicates support for slow-motion, it is application's responsibility 616 * to parse the slow-motion videos using their own parser or using support library. 617 * @see android.media.MediaFormat#KEY_SLOW_MOTION_MARKERS 618 * @hide 619 */ 620 @NonNull setSlowMotionSupported(boolean slowMotionSupported)621 public Builder setSlowMotionSupported(boolean slowMotionSupported) { 622 mIsSlowMotionSupported = slowMotionSupported; 623 return this; 624 } 625 } 626 } 627