From 049b6edbb357f564ecb977771f6af9adb312934a Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 May 2021 20:34:53 +0100 Subject: [PATCH 01/48] Update PlayerNotficationManager to set PendingIntent.FLAG_IMMUTABLE on its Broadcast intent. In Android 12 mutability flags have to be set on PendingIntents. If they are not, and the app targets Android 12, then the app will be crashed by the system. PiperOrigin-RevId: 373427591 --- RELEASENOTES.md | 7 +++++++ .../exoplayer2/ui/PlayerNotificationManager.java | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 30eee5883b..e0c1e0da70 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,12 @@ # Release notes +### dev-v2 (not yet released) + +* UI: + * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent + in `PlayerNotificationManager`. This is required to avoid an error on + Android 12. + ### 2.14.1 (2021-06-11) * Core Library: diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 3139cd451d..6c3441a849 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -1598,8 +1598,15 @@ public class PlayerNotificationManager { String action, Context context, int instanceId) { Intent intent = new Intent(action).setPackage(context.getPackageName()); intent.putExtra(EXTRA_INSTANCE_ID, instanceId); - return PendingIntent.getBroadcast( - context, instanceId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + int pendingFlags; + if (Util.SDK_INT >= 23) { + pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; + } else { + pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT; + } + + return PendingIntent.getBroadcast(context, instanceId, intent, pendingFlags); } @SuppressWarnings("nullness:argument.type.incompatible") From 7b96ba050752c153d647abf17579e44b6625de33 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 8 Jun 2021 11:51:26 +0100 Subject: [PATCH 02/48] Bump the androidx annotation version to 1.2.0 It is the current public stable version and it brings in @DoNotInline. PiperOrigin-RevId: 378119413 --- constants.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants.gradle b/constants.gradle index 01b6746051..8a1fe8addd 100644 --- a/constants.gradle +++ b/constants.gradle @@ -29,7 +29,7 @@ project.ext { checkerframeworkCompatVersion = '2.5.0' jsr305Version = '3.0.2' kotlinAnnotationsVersion = '1.3.70' - androidxAnnotationVersion = '1.1.0' + androidxAnnotationVersion = '1.2.0' androidxAppCompatVersion = '1.1.0' androidxCollectionVersion = '1.1.0' androidxCoreVersion = '1.3.2' From 19e3d9b8ca9202b17d2e95547a07d2d20b765228 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 10 Jun 2021 10:18:36 +0100 Subject: [PATCH 03/48] Bump dexmaker version Issue: #9032 PiperOrigin-RevId: 378605169 --- constants.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants.gradle b/constants.gradle index 8a1fe8addd..da35070f99 100644 --- a/constants.gradle +++ b/constants.gradle @@ -19,7 +19,7 @@ project.ext { appTargetSdkVersion = 29 targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest. compileSdkVersion = 30 - dexmakerVersion = '2.21.0' + dexmakerVersion = '2.28.1' junitVersion = '4.13.2' guavaVersion = '27.1-android' mockitoVersion = '2.28.2' From 9a876f730092de29bae8cd588877077e51ec48b3 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 10 Jun 2021 10:30:22 +0100 Subject: [PATCH 04/48] Fix IncorrectContextUseViolation on Android 11 Issue: #8246 PiperOrigin-RevId: 378606475 --- RELEASENOTES.md | 3 + .../video/VideoFrameReleaseHelper.java | 145 ++++++++++++------ 2 files changed, 103 insertions(+), 45 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e0c1e0da70..13b202f613 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,9 @@ ### dev-v2 (not yet released) +* Video: + * Fix `IncorrectContextUseViolation` strict mode warning on Android 11 + ([#8246](https://github.com/google/ExoPlayer/pull/8246)). * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseHelper.java index 1778ed6976..bb3b0b64c2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseHelper.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.video; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import android.annotation.TargetApi; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.Handler; @@ -91,9 +90,8 @@ public final class VideoFrameReleaseHelper { private static final long VSYNC_OFFSET_PERCENTAGE = 80; private final FixedFrameRateEstimator frameRateEstimator; - @Nullable private final WindowManager windowManager; + @Nullable private final DisplayHelper displayHelper; @Nullable private final VSyncSampler vsyncSampler; - @Nullable private final DefaultDisplayListener displayListener; private boolean started; @Nullable private Surface surface; @@ -127,20 +125,8 @@ public final class VideoFrameReleaseHelper { */ public VideoFrameReleaseHelper(@Nullable Context context) { frameRateEstimator = new FixedFrameRateEstimator(); - if (context != null) { - context = context.getApplicationContext(); - windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - } else { - windowManager = null; - } - if (windowManager != null) { - displayListener = - Util.SDK_INT >= 17 ? maybeBuildDefaultDisplayListenerV17(checkNotNull(context)) : null; - vsyncSampler = VSyncSampler.getInstance(); - } else { - displayListener = null; - vsyncSampler = null; - } + displayHelper = maybeBuildDisplayHelper(context); + vsyncSampler = displayHelper != null ? VSyncSampler.getInstance() : null; vsyncDurationNs = C.TIME_UNSET; vsyncOffsetNs = C.TIME_UNSET; formatFrameRate = Format.NO_VALUE; @@ -148,14 +134,10 @@ public final class VideoFrameReleaseHelper { } /** Called when the renderer is enabled. */ - @TargetApi(17) // displayListener is null if Util.SDK_INT < 17. public void onEnabled() { - if (windowManager != null) { + if (displayHelper != null) { checkNotNull(vsyncSampler).addObserver(); - if (displayListener != null) { - displayListener.register(); - } - updateDefaultDisplayRefreshRateParams(); + displayHelper.register(this::updateDefaultDisplayRefreshRateParams); } } @@ -233,12 +215,9 @@ public final class VideoFrameReleaseHelper { } /** Called when the renderer is disabled. */ - @TargetApi(17) // displayListener is null if Util.SDK_INT < 17. public void onDisabled() { - if (windowManager != null) { - if (displayListener != null) { - displayListener.unregister(); - } + if (displayHelper != null) { + displayHelper.unregister(); checkNotNull(vsyncSampler).removeObserver(); } } @@ -394,15 +373,7 @@ public final class VideoFrameReleaseHelper { // Display refresh rate and vsync logic. - @RequiresApi(17) - @Nullable - private DefaultDisplayListener maybeBuildDefaultDisplayListenerV17(Context context) { - DisplayManager manager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - return manager == null ? null : new DefaultDisplayListener(manager); - } - - private void updateDefaultDisplayRefreshRateParams() { - Display defaultDisplay = checkNotNull(windowManager).getDefaultDisplay(); + private void updateDefaultDisplayRefreshRateParams(@Nullable Display defaultDisplay) { if (defaultDisplay != null) { double defaultDisplayRefreshRate = defaultDisplay.getRefreshRate(); vsyncDurationNs = (long) (C.NANOS_PER_SECOND / defaultDisplayRefreshRate); @@ -431,21 +402,108 @@ public final class VideoFrameReleaseHelper { return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; } + @Nullable + private static DisplayHelper maybeBuildDisplayHelper(@Nullable Context context) { + @Nullable DisplayHelper displayHelper = null; + if (context != null) { + context = context.getApplicationContext(); + if (Util.SDK_INT >= 17) { + displayHelper = DisplayHelperV17.maybeBuildNewInstance(context); + } + if (displayHelper == null) { + displayHelper = DisplayHelperV16.maybeBuildNewInstance(context); + } + } + return displayHelper; + } + + /** Helper for listening to changes to the default display. */ + private interface DisplayHelper { + + /** Listener for changes to the default display. */ + interface Listener { + + /** + * Called when the default display changes. + * + * @param defaultDisplay The default display, or {@code null} if a corresponding {@link + * Display} object could not be obtained. + */ + void onDefaultDisplayChanged(@Nullable Display defaultDisplay); + } + + /** + * Enables the helper, invoking {@link Listener#onDefaultDisplayChanged(Display)} to pass the + * initial default display. + */ + void register(Listener listener); + + /** Disables the helper. */ + void unregister(); + } + + private static final class DisplayHelperV16 implements DisplayHelper { + + @Nullable + public static DisplayHelper maybeBuildNewInstance(Context context) { + WindowManager windowManager = + (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + return windowManager != null ? new DisplayHelperV16(windowManager) : null; + } + + private final WindowManager windowManager; + + private DisplayHelperV16(WindowManager windowManager) { + this.windowManager = windowManager; + } + + @Override + public void register(Listener listener) { + listener.onDefaultDisplayChanged(windowManager.getDefaultDisplay()); + } + + @Override + public void unregister() { + // Do nothing. + } + } + @RequiresApi(17) - private final class DefaultDisplayListener implements DisplayManager.DisplayListener { + private static final class DisplayHelperV17 + implements DisplayHelper, DisplayManager.DisplayListener { + + @Nullable + public static DisplayHelper maybeBuildNewInstance(Context context) { + DisplayManager displayManager = + (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + return displayManager != null ? new DisplayHelperV17(displayManager) : null; + } private final DisplayManager displayManager; + @Nullable private Listener listener; - public DefaultDisplayListener(DisplayManager displayManager) { + private DisplayHelperV17(DisplayManager displayManager) { this.displayManager = displayManager; } - public void register() { + @Override + public void register(Listener listener) { + this.listener = listener; displayManager.registerDisplayListener(this, Util.createHandlerForCurrentLooper()); + listener.onDefaultDisplayChanged(getDefaultDisplay()); } + @Override public void unregister() { displayManager.unregisterDisplayListener(this); + listener = null; + } + + @Override + public void onDisplayChanged(int displayId) { + if (listener != null && displayId == Display.DEFAULT_DISPLAY) { + listener.onDefaultDisplayChanged(getDefaultDisplay()); + } } @Override @@ -458,11 +516,8 @@ public final class VideoFrameReleaseHelper { // Do nothing. } - @Override - public void onDisplayChanged(int displayId) { - if (displayId == Display.DEFAULT_DISPLAY) { - updateDefaultDisplayRefreshRateParams(); - } + private Display getDefaultDisplay() { + return displayManager.getDisplay(Display.DEFAULT_DISPLAY); } } From 3ae4c1b07bff8de4ca9fce848ebd3570e30f8a7a Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 15 Jun 2021 18:26:27 +0100 Subject: [PATCH 05/48] Merge pull request #6500 from DolbyLaboratories:dev-v2-isDirectPlaybackSupported PiperOrigin-RevId: 378895355 --- RELEASENOTES.md | 5 ++ .../exoplayer2/audio/AudioCapabilities.java | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 13b202f613..c1f204e06f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,11 @@ * Video: * Fix `IncorrectContextUseViolation` strict mode warning on Android 11 ([#8246](https://github.com/google/ExoPlayer/pull/8246)). +* Audio: + * Use `AudioTrack.isDirectPlaybackSupported` to check for encoded audio + passthrough capability from API 29 onwards, instead of using the HDMI + audio plug intent + ([#6500](https://github.com/google/ExoPlayer/pull/6500)). * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java index 9dee990905..643f91078e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java @@ -21,23 +21,30 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioFormat; import android.media.AudioManager; +import android.media.AudioTrack; import android.net.Uri; import android.provider.Settings.Global; +import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Ints; import java.util.Arrays; /** Represents the set of audio formats that a device is capable of playing. */ public final class AudioCapabilities { private static final int DEFAULT_MAX_CHANNEL_COUNT = 8; + private static final int DEFAULT_SAMPLE_RATE_HZ = 48_000; /** The minimum audio capabilities supported by all devices. */ public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES = new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, DEFAULT_MAX_CHANNEL_COUNT); /** Audio capabilities when the device specifies external surround sound. */ + @SuppressWarnings("InlinedApi") private static final AudioCapabilities EXTERNAL_SURROUND_SOUND_CAPABILITIES = new AudioCapabilities( new int[] { @@ -45,6 +52,19 @@ public final class AudioCapabilities { }, DEFAULT_MAX_CHANNEL_COUNT); + /** Array of all surround sound encodings that a device may be capable of playing. */ + @SuppressWarnings("InlinedApi") + private static final int[] ALL_SURROUND_ENCODINGS = + new int[] { + AudioFormat.ENCODING_AC3, + AudioFormat.ENCODING_E_AC3, + AudioFormat.ENCODING_E_AC3_JOC, + AudioFormat.ENCODING_AC4, + AudioFormat.ENCODING_DOLBY_TRUEHD, + AudioFormat.ENCODING_DTS, + AudioFormat.ENCODING_DTS_HD, + }; + /** Global settings key for devices that can specify external surround sound. */ private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled"; @@ -68,6 +88,10 @@ public final class AudioCapabilities { && Global.getInt(context.getContentResolver(), EXTERNAL_SURROUND_SOUND_KEY, 0) == 1) { return EXTERNAL_SURROUND_SOUND_CAPABILITIES; } + if (Util.SDK_INT >= 29) { + return new AudioCapabilities( + AudioTrackWrapperV29.getDirectPlaybackSupportedEncodingsV29(), DEFAULT_MAX_CHANNEL_COUNT); + } if (intent == null || intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 0) { return DEFAULT_AUDIO_CAPABILITIES; } @@ -158,4 +182,29 @@ public final class AudioCapabilities { return Util.SDK_INT >= 17 && ("Amazon".equals(Util.MANUFACTURER) || "Xiaomi".equals(Util.MANUFACTURER)); } + + @RequiresApi(29) + private static final class AudioTrackWrapperV29 { + @DoNotInline + public static int[] getDirectPlaybackSupportedEncodingsV29() { + ImmutableList.Builder supportedEncodingsListBuilder = ImmutableList.builder(); + for (int encoding : ALL_SURROUND_ENCODINGS) { + if (AudioTrack.isDirectPlaybackSupported( + new AudioFormat.Builder() + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) + .setEncoding(encoding) + .setSampleRate(DEFAULT_SAMPLE_RATE_HZ) + .build(), + new android.media.AudioAttributes.Builder() + .setUsage(android.media.AudioAttributes.USAGE_MEDIA) + .setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE) + .setFlags(0) + .build())) { + supportedEncodingsListBuilder.add(encoding); + } + } + supportedEncodingsListBuilder.add(AudioFormat.ENCODING_PCM_16BIT); + return Ints.toArray(supportedEncodingsListBuilder.build()); + } + } } From d3a0709374b6d8b399c8fdebfd99128d54450e85 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 15 Jun 2021 18:39:14 +0100 Subject: [PATCH 06/48] Merge pull request #9023 from DolbyLaboratories:dev-v2-multicodecs PiperOrigin-RevId: 379440699 --- RELEASENOTES.md | 1 + .../java/com/google/android/exoplayer2/audio/Ac3Util.java | 7 +++++++ .../java/com/google/android/exoplayer2/util/MimeTypes.java | 3 ++- .../source/dash/manifest/DashManifestParser.java | 4 ++++ .../exoplayer2/source/hls/playlist/HlsPlaylistParser.java | 2 ++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c1f204e06f..86c3418d46 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,7 @@ * Fix `IncorrectContextUseViolation` strict mode warning on Android 11 ([#8246](https://github.com/google/ExoPlayer/pull/8246)). * Audio: + * Fix track selection for E-AC-3 streams. * Use `AudioTrack.isDirectPlaybackSupported` to check for encoded audio passthrough capability from API 29 onwards, instead of using the HDMI audio plug intent diff --git a/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index f9a97d961f..8cbd53d1b7 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -100,6 +100,13 @@ public final class Ac3Util { } + /** + * A non-standard codec string for E-AC-3. Use of this constant allows for disambiguation between + * regular AC-3 ("ec-3") and E-AC-3 ("ec+3") streams from the codec string alone. The standard is + * to use "ec-3" for both, as per the MP4RA registered codec + * types. + */ + public static final String E_AC_3_CODEC_STRING = "ec+3"; /** Maximum rate for an AC-3 audio stream, in bytes per second. */ public static final int AC3_MAX_RATE_BYTES_PER_SECOND = 640 * 1000 / 8; /** Maximum rate for an E-AC-3 audio stream, in bytes per second. */ diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index ba7fb0090b..066cdc9ea4 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -20,6 +20,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.audio.AacUtil; +import com.google.android.exoplayer2.audio.Ac3Util; import com.google.common.base.Ascii; import java.util.ArrayList; import java.util.regex.Matcher; @@ -375,7 +376,7 @@ public final class MimeTypes { return MimeTypes.AUDIO_AC3; } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { return MimeTypes.AUDIO_E_AC3; - } else if (codec.startsWith("ec+3")) { + } else if (codec.startsWith(Ac3Util.E_AC_3_CODEC_STRING)) { return MimeTypes.AUDIO_E_AC3_JOC; } else if (codec.startsWith("ac-4") || codec.startsWith("dac4")) { return MimeTypes.AUDIO_AC4; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 6fc6038f50..bf72353cfc 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -24,6 +24,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; @@ -755,6 +756,9 @@ public class DashManifestParser extends DefaultHandler @Nullable String sampleMimeType = getSampleMimeType(containerMimeType, codecs); if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) { sampleMimeType = parseEac3SupplementalProperties(supplementalProperties); + if (MimeTypes.AUDIO_E_AC3_JOC.equals(sampleMimeType)) { + codecs = Ac3Util.E_AC_3_CODEC_STRING; + } } @C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors); @C.RoleFlags int roleFlags = parseRoleFlagsFromRoleDescriptors(roleDescriptors); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index c24676de88..d781357480 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -26,6 +26,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; @@ -507,6 +508,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Tue, 15 Jun 2021 16:48:46 +0100 Subject: [PATCH 07/48] Add DRM info to Format.toLogString The Widevine H264 samples in the demo app now log this from the EventLogger: ``` [X] Track:0, id=1, mimeType=video/avc, bitrate=772315, codecs=avc1.42c01e, drm=[widevine,cenc], res=320x142, fps=24.0, supported=YES ``` And the VP9 ones log: ``` [X] Track:0, id=1, mimeType=video/x-vnd.on2.vp9, bitrate=588256, codecs=vp9, drm=[widevine], res=320x142, fps=23.809525, supported=YES ``` #minor-release PiperOrigin-RevId: 379498332 --- .../com/google/android/exoplayer2/Format.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index 05062727c3..1f3e5bf824 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; @@ -22,14 +24,17 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.UnsupportedMediaCrypto; import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; +import com.google.common.base.Joiner; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import java.util.UUID; /** * Represents a media format. @@ -1249,7 +1254,7 @@ public final class Format implements Parcelable { int initializationDataSize = in.readInt(); initializationData = new ArrayList<>(initializationDataSize); for (int i = 0; i < initializationDataSize; i++) { - initializationData.add(Assertions.checkNotNull(in.createByteArray())); + initializationData.add(checkNotNull(in.createByteArray())); } drmInitData = in.readParcelable(DrmInitData.class.getClassLoader()); subsampleOffsetUs = in.readLong(); @@ -1587,6 +1592,26 @@ public final class Format implements Parcelable { if (format.codecs != null) { builder.append(", codecs=").append(format.codecs); } + if (format.drmInitData != null) { + Set schemes = new LinkedHashSet<>(); + for (int i = 0; i < format.drmInitData.schemeDataCount; i++) { + UUID schemeUuid = format.drmInitData.get(i).uuid; + if (schemeUuid.equals(C.COMMON_PSSH_UUID)) { + schemes.add("cenc"); + } else if (schemeUuid.equals(C.CLEARKEY_UUID)) { + schemes.add("clearkey"); + } else if (schemeUuid.equals(C.PLAYREADY_UUID)) { + schemes.add("playready"); + } else if (schemeUuid.equals(C.WIDEVINE_UUID)) { + schemes.add("widevine"); + } else if (schemeUuid.equals(C.UUID_NIL)) { + schemes.add("universal"); + } else { + schemes.add("unknown (" + schemeUuid + ")"); + } + } + builder.append(", drm=[").append(Joiner.on(',').join(schemes)).append(']'); + } if (format.width != NO_VALUE && format.height != NO_VALUE) { builder.append(", res=").append(format.width).append("x").append(format.height); } From 08dbfd5c5ad9fdb9d0855d8ffc02096b6235052d Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 15 Jun 2021 17:27:44 +0100 Subject: [PATCH 08/48] Fix issue where a trun atom could be associated with the wrong track Note that this removes a workaround for malformed content, in which the track_ID is set incorrectly. It's unclear there was sufficient reason to implement that workaround, and so it's preferable to remove it, rather than implementing the concept of unrecognized tracks, which would be needed to keep it and to also fix this issue. Issue: #9056 PiperOrigin-RevId: 379506261 --- RELEASENOTES.md | 8 +++++ .../extractor/mp4/FragmentedMp4Extractor.java | 31 ++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 86c3418d46..7dd85a2979 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,14 @@ passthrough capability from API 29 onwards, instead of using the HDMI audio plug intent ([#6500](https://github.com/google/ExoPlayer/pull/6500)). +* Extractors: + * Fix issue where a `trun` atom could be associated with the wrong track + in an FMP4 stream + ([#9056](https://github.com/google/ExoPlayer/pull/9056)). The fix + removes a previous workaround to handle content in which the `track_ID` + is set incorrectly + ([#4083](https://github.com/google/ExoPlayer/issues/4083)). Such content + is malformed and should be re-encoded. * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index e14381c564..ef71d82559 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -557,7 +557,7 @@ public class FragmentedMp4Extractor implements Extractor { } private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { - parseMoof(moof, trackBundles, flags, scratchBytes); + parseMoof(moof, trackBundles, sideloadedTrack != null, flags, scratchBytes); @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren); if (drmInitData != null) { @@ -699,14 +699,15 @@ public class FragmentedMp4Extractor implements Extractor { return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong(); } - private static void parseMoof(ContainerAtom moof, SparseArray trackBundleArray, + private static void parseMoof(ContainerAtom moof, SparseArray trackBundles, + boolean haveSideloadedTrack, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { int moofContainerChildrenSize = moof.containerChildren.size(); for (int i = 0; i < moofContainerChildrenSize; i++) { Atom.ContainerAtom child = moof.containerChildren.get(i); // TODO: Support multiple traf boxes per track in a single moof. if (child.type == Atom.TYPE_traf) { - parseTraf(child, trackBundleArray, flags, extendedTypeScratch); + parseTraf(child, trackBundles, haveSideloadedTrack, flags, extendedTypeScratch); } } } @@ -714,10 +715,11 @@ public class FragmentedMp4Extractor implements Extractor { /** * Parses a traf atom (defined in 14496-12). */ - private static void parseTraf(ContainerAtom traf, SparseArray trackBundleArray, + private static void parseTraf(ContainerAtom traf, SparseArray trackBundles, + boolean haveSideloadedTrack, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = checkNotNull(traf.getLeafAtomOfType(Atom.TYPE_tfhd)); - @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray); + @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundles, haveSideloadedTrack); if (trackBundle == null) { return; } @@ -874,17 +876,21 @@ public class FragmentedMp4Extractor implements Extractor { * * @param tfhd The tfhd atom to decode. * @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed. + * @param haveSideloadedTrack Whether {@code trackBundles} contains a single bundle corresponding + * to a side-loaded track. * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd * does not refer to any {@link TrackBundle}. */ @Nullable private static TrackBundle parseTfhd( - ParsableByteArray tfhd, SparseArray trackBundles) { + ParsableByteArray tfhd, SparseArray trackBundles, boolean haveSideloadedTrack) { tfhd.setPosition(Atom.HEADER_SIZE); int fullAtom = tfhd.readInt(); int atomFlags = Atom.parseFullAtomFlags(fullAtom); int trackId = tfhd.readInt(); - @Nullable TrackBundle trackBundle = getTrackBundle(trackBundles, trackId); + @Nullable + TrackBundle trackBundle = + haveSideloadedTrack ? trackBundles.valueAt(0) : trackBundles.get(trackId); if (trackBundle == null) { return null; } @@ -916,17 +922,6 @@ public class FragmentedMp4Extractor implements Extractor { return trackBundle; } - private static @Nullable TrackBundle getTrackBundle( - SparseArray trackBundles, int trackId) { - if (trackBundles.size() == 1) { - // Ignore track id if there is only one track. This is either because we have a side-loaded - // track or to cope with non-matching track indices (see - // https://github.com/google/ExoPlayer/issues/4083). - return trackBundles.valueAt(/* index= */ 0); - } - return trackBundles.get(trackId); - } - /** * Parses a tfdt atom (defined in 14496-12). * From c269a62fe0f72ccfaed0a1afd960b5356a94257b Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 15 Jun 2021 18:16:21 +0100 Subject: [PATCH 09/48] HLS: Fix issue where new init segment would not be loaded Issue: #9004 PiperOrigin-RevId: 379516815 --- RELEASENOTES.md | 5 +++++ .../android/exoplayer2/source/hls/HlsMediaChunk.java | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7dd85a2979..95c8f33560 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,11 @@ is set incorrectly ([#4083](https://github.com/google/ExoPlayer/issues/4083)). Such content is malformed and should be re-encoded. +* HLS: + * Fix issue where a new initialization segment, as specified by an + `EXT-X-MAP` tag in a media playlist, would not be loaded when + encountered during playback + ([#9004](https://github.com/google/ExoPlayer/issues/9004)). * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 49a93ed806..e6295cf82b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -139,12 +139,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ParsableByteArray scratchId3Data; if (previousChunk != null) { + boolean isSameInitData = + initDataSpec == previousChunk.initDataSpec + || (initDataSpec != null + && previousChunk.initDataSpec != null + && initDataSpec.uri.equals(previousChunk.initDataSpec.uri) + && initDataSpec.position == previousChunk.initDataSpec.position); boolean isFollowingChunk = playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted; id3Decoder = previousChunk.id3Decoder; scratchId3Data = previousChunk.scratchId3Data; previousExtractor = - isFollowingChunk + isSameInitData + && isFollowingChunk && !previousChunk.extractorInvalidated && previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber ? previousChunk.extractor From 4190ff3274676a58f648ce5b7d0d51acf905ce6e Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 17 Jun 2021 10:39:53 +0100 Subject: [PATCH 10/48] Add a test for the provisioning flow to DefaultDrmSessionManagerTest #minor-release PiperOrigin-RevId: 379913814 --- .../drm/DefaultDrmSessionManagerTest.java | 35 ++++++-- .../exoplayer2/testutil/FakeExoMediaDrm.java | 85 ++++++++++++++++--- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java index d58a55e444..cc3c5f0bd7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java @@ -43,9 +43,7 @@ import org.robolectric.shadows.ShadowLooper; // TODO: Test more branches: // - Different sources for licenseServerUrl. // - Multiple acquisitions & releases for same keys -> multiple requests. -// - Provisioning. // - Key denial. -// - Handling of ResourceBusyException (indicating session scarcity). @RunWith(AndroidJUnit4.class) public class DefaultDrmSessionManagerTest { @@ -538,6 +536,33 @@ public class DefaultDrmSessionManagerTest { exoMediaDrm.release(); } + @Test + public void deviceNotProvisioned_provisioningDoneAndOpenSessionRetried() { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + + DefaultDrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider( + DRM_SCHEME_UUID, + uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(1).build()) + .build(/* mediaDrmCallback= */ licenseServer); + drmSessionManager.prepare(); + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + // Confirm the device isn't provisioned (otherwise state would be OPENED) + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING); + waitForOpenedWithKeys(drmSession); + + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + assertThat(drmSession.queryKeyStatus()) + .containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE); + } + @Test public void managerNotPrepared_acquireSessionAndPreacquireSessionFail() throws Exception { FakeExoMediaDrm.LicenseServer licenseServer = @@ -602,10 +627,10 @@ public class DefaultDrmSessionManagerTest { } private static void waitForOpenedWithKeys(DrmSession drmSession) { - // Check the error first, so we get a meaningful failure if there's been an error. - assertThat(drmSession.getError()).isNull(); - assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED); while (drmSession.getState() != DrmSession.STATE_OPENED_WITH_KEYS) { + // Check the error first, so we get a meaningful failure if there's been an error. + assertThat(drmSession.getError()).isNull(); + assertThat(drmSession.getState()).isAnyOf(DrmSession.STATE_OPENING, DrmSession.STATE_OPENED); // Allow the key response to be handled. ShadowLooper.idleMainLooper(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java index 14663985ab..608347c569 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java @@ -39,6 +39,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Bytes; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -61,8 +62,59 @@ import java.util.concurrent.atomic.AtomicInteger; @RequiresApi(29) public final class FakeExoMediaDrm implements ExoMediaDrm { + /** Builder for {@link FakeExoMediaDrm} instances. */ + public static class Builder { + private int provisionsRequired; + private int maxConcurrentSessions; + + /** Constructs an instance. */ + public Builder() { + provisionsRequired = 0; + maxConcurrentSessions = Integer.MAX_VALUE; + } + + /** + * Sets how many successful provisioning round trips are needed for the {@link FakeExoMediaDrm} + * to be provisioned. + * + *

An unprovisioned {@link FakeExoMediaDrm} will throw {@link NotProvisionedException} from + * {@link FakeExoMediaDrm#openSession()} until enough valid provisioning responses are passed to + * {@link FakeExoMediaDrm#provideProvisionResponse(byte[])}. + * + *

Defaults to 0 (i.e. device is already provisioned). + */ + public Builder setProvisionsRequired(int provisionsRequired) { + this.provisionsRequired = provisionsRequired; + return this; + } + + /** + * Sets the maximum number of concurrent sessions the {@link FakeExoMediaDrm} will support. + * + *

If this is exceeded then subsequent calls to {@link FakeExoMediaDrm#openSession()} will + * throw {@link ResourceBusyException}. + * + *

Defaults to {@link Integer#MAX_VALUE}. + */ + public Builder setMaxConcurrentSessions(int maxConcurrentSessions) { + this.maxConcurrentSessions = maxConcurrentSessions; + return this; + } + + /** + * Returns a {@link FakeExoMediaDrm} instance with an initial reference count of 1. The caller + * is responsible for calling {@link FakeExoMediaDrm#release()} when they no longer need the + * instance. + */ + public FakeExoMediaDrm build() { + return new FakeExoMediaDrm(provisionsRequired, maxConcurrentSessions); + } + } + public static final ProvisionRequest FAKE_PROVISION_REQUEST = new ProvisionRequest(TestUtil.createByteArray(7, 8, 9), "bar.test"); + public static final ImmutableList VALID_PROVISION_RESPONSE = + TestUtil.createByteList(4, 5, 6); /** Key for use with the Map returned from {@link FakeExoMediaDrm#queryKeyStatus(byte[])}. */ public static final String KEY_STATUS_KEY = "KEY_STATUS"; @@ -74,6 +126,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { private static final ImmutableList VALID_KEY_RESPONSE = TestUtil.createByteList(1, 2, 3); private static final ImmutableList KEY_DENIED_RESPONSE = TestUtil.createByteList(9, 8, 7); + private final int provisionsRequired; private final int maxConcurrentSessions; private final Map byteProperties; private final Map stringProperties; @@ -81,24 +134,24 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { private final Set> sessionIdsWithValidKeys; private final AtomicInteger sessionIdGenerator; + private int provisionsReceived; private int referenceCount; @Nullable private OnEventListener onEventListener; - /** - * Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls - * to {@link #openSession()} with no limit on the number of concurrent open sessions. - */ + /** @deprecated Use {@link Builder} instead. */ + @Deprecated public FakeExoMediaDrm() { this(/* maxConcurrentSessions= */ Integer.MAX_VALUE); } - /** - * Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls - * to {@link #openSession()} with a limit on the number of concurrent open sessions. - * - * @param maxConcurrentSessions The max number of sessions allowed to be open simultaneously. - */ + /** @deprecated Use {@link Builder} instead. */ + @Deprecated public FakeExoMediaDrm(int maxConcurrentSessions) { + this(/* provisionsRequired= */ 0, maxConcurrentSessions); + } + + private FakeExoMediaDrm(int provisionsRequired, int maxConcurrentSessions) { + this.provisionsRequired = provisionsRequired; this.maxConcurrentSessions = maxConcurrentSessions; byteProperties = new HashMap<>(); stringProperties = new HashMap<>(); @@ -129,6 +182,9 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public byte[] openSession() throws MediaDrmException { Assertions.checkState(referenceCount > 0); + if (provisionsReceived < provisionsRequired) { + throw new NotProvisionedException("Not provisioned."); + } if (openSessionIds.size() >= maxConcurrentSessions) { throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions); } @@ -200,6 +256,9 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public void provideProvisionResponse(byte[] response) throws DeniedByServerException { Assertions.checkState(referenceCount > 0); + if (Bytes.asList(response).equals(VALID_PROVISION_RESPONSE)) { + provisionsReceived++; + } } @Override @@ -340,7 +399,11 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws MediaDrmCallbackException { - return new byte[0]; + if (Arrays.equals(request.getData(), FAKE_PROVISION_REQUEST.getData())) { + return Bytes.toArray(VALID_PROVISION_RESPONSE); + } else { + return Util.EMPTY_BYTE_ARRAY; + } } @Override From f3e62343fcb82579e587e03c987e66ca68ab10fe Mon Sep 17 00:00:00 2001 From: christosts Date: Thu, 17 Jun 2021 11:43:26 +0100 Subject: [PATCH 11/48] Forward FRAME-RATE from the master playlist to renditions Issue: #8960 PiperOrigin-RevId: 379922704 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/source/hls/HlsSampleStreamWrapper.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 95c8f33560..ccf6d2f7c2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,8 @@ `EXT-X-MAP` tag in a media playlist, would not be loaded when encountered during playback ([#9004](https://github.com/google/ExoPlayer/issues/9004)). + * Forward the FRAME-RATE value from the master playlist to renditions. + ([#8960](https://github.com/google/ExoPlayer/issues/8960)). * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 37468bfb1d..0bca1b2198 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -1502,7 +1502,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; .setPeakBitrate(propagateBitrates ? playlistFormat.peakBitrate : Format.NO_VALUE) .setCodecs(codecs) .setWidth(playlistFormat.width) - .setHeight(playlistFormat.height); + .setHeight(playlistFormat.height) + .setFrameRate(playlistFormat.frameRate); if (sampleMimeType != null) { formatBuilder.setSampleMimeType(sampleMimeType); From ed563183cd442029e25196d659134e0d1297954c Mon Sep 17 00:00:00 2001 From: christosts Date: Thu, 17 Jun 2021 13:18:15 +0100 Subject: [PATCH 12/48] Set master playlist's channelCount only on audio renditions PiperOrigin-RevId: 379935363 --- .../source/hls/HlsSampleStreamWrapper.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 0bca1b2198..6e16abc519 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -1500,16 +1500,20 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; .setRoleFlags(playlistFormat.roleFlags) .setAverageBitrate(propagateBitrates ? playlistFormat.averageBitrate : Format.NO_VALUE) .setPeakBitrate(propagateBitrates ? playlistFormat.peakBitrate : Format.NO_VALUE) - .setCodecs(codecs) - .setWidth(playlistFormat.width) - .setHeight(playlistFormat.height) - .setFrameRate(playlistFormat.frameRate); + .setCodecs(codecs); + + if (sampleTrackType == C.TRACK_TYPE_VIDEO) { + formatBuilder + .setWidth(playlistFormat.width) + .setHeight(playlistFormat.height) + .setFrameRate(playlistFormat.frameRate); + } if (sampleMimeType != null) { formatBuilder.setSampleMimeType(sampleMimeType); } - if (playlistFormat.channelCount != Format.NO_VALUE) { + if (playlistFormat.channelCount != Format.NO_VALUE && sampleTrackType == C.TRACK_TYPE_AUDIO) { formatBuilder.setChannelCount(playlistFormat.channelCount); } From a27d9a04b87b607d9483fff8211c484025612d34 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 18 Jun 2021 15:23:28 +0100 Subject: [PATCH 13/48] Allow repeated DRM provisioning in DefaultDrmSessionManager Also change to explicitly track the provisioning session, which makes the code easier to reason about than always using the zero'th element of the list. PiperOrigin-RevId: 380181453 --- RELEASENOTES.md | 2 + .../exoplayer2/drm/DefaultDrmSession.java | 14 ++--- .../drm/DefaultDrmSessionManager.java | 59 +++++++++++------ .../drm/DefaultDrmSessionManagerTest.java | 63 +++++++++++++++++++ .../exoplayer2/testutil/FakeExoMediaDrm.java | 25 ++++++-- 5 files changed, 129 insertions(+), 34 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ccf6d2f7c2..cb244b6c94 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -26,6 +26,8 @@ ([#9004](https://github.com/google/ExoPlayer/issues/9004)). * Forward the FRAME-RATE value from the master playlist to renditions. ([#8960](https://github.com/google/ExoPlayer/issues/8960)). +* DRM: + * Allow repeated provisioning in `DefaultDrmSession(Manager)`. * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index fa9f428829..f4932590ab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -230,7 +230,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } public void onProvisionCompleted() { - if (openInternal(false)) { + if (openInternal()) { doLicense(true); } } @@ -290,7 +290,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; requestHandlerThread = new HandlerThread("ExoPlayer:DrmRequestHandler"); requestHandlerThread.start(); requestHandler = new RequestHandler(requestHandlerThread.getLooper()); - if (openInternal(true)) { + if (openInternal()) { doLicense(true); } } else if (eventDispatcher != null @@ -338,12 +338,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * Try to open a session, do provisioning if necessary. * - * @param allowProvisioning if provisioning is allowed, set this to false when calling from - * processing provision response. * @return true on success, false otherwise. */ @EnsuresNonNullIf(result = true, expression = "sessionId") - private boolean openInternal(boolean allowProvisioning) { + private boolean openInternal() { if (isOpen()) { // Already opened return true; @@ -359,11 +357,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; Assertions.checkNotNull(sessionId); return true; } catch (NotProvisionedException e) { - if (allowProvisioning) { - provisioningManager.provisionRequired(this); - } else { - onError(e); - } + provisioningManager.provisionRequired(this); } catch (Exception e) { onError(e); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index ffdfb779e5..093257e870 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -46,6 +46,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -289,7 +290,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager { private final long sessionKeepaliveMs; private final List sessions; - private final List provisioningSessions; private final Set preacquiredSessionReferences; private final Set keepaliveSessions; @@ -411,7 +411,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager { referenceCountListener = new ReferenceCountListenerImpl(); mode = MODE_PLAYBACK; sessions = new ArrayList<>(); - provisioningSessions = new ArrayList<>(); preacquiredSessionReferences = Sets.newIdentityHashSet(); keepaliveSessions = Sets.newIdentityHashSet(); this.sessionKeepaliveMs = sessionKeepaliveMs; @@ -842,33 +841,60 @@ public class DefaultDrmSessionManager implements DrmSessionManager { } private class ProvisioningManagerImpl implements DefaultDrmSession.ProvisioningManager { + + private final Set sessionsAwaitingProvisioning; + @Nullable private DefaultDrmSession provisioningSession; + + public ProvisioningManagerImpl() { + sessionsAwaitingProvisioning = new HashSet<>(); + } + @Override public void provisionRequired(DefaultDrmSession session) { - if (provisioningSessions.contains(session)) { - // The session has already requested provisioning. + sessionsAwaitingProvisioning.add(session); + if (provisioningSession != null) { + // Provisioning is already in-flight. return; } - provisioningSessions.add(session); - if (provisioningSessions.size() == 1) { - // This is the first session requesting provisioning, so have it perform the operation. - session.provision(); - } + provisioningSession = session; + session.provision(); } @Override public void onProvisionCompleted() { - for (DefaultDrmSession session : provisioningSessions) { + provisioningSession = null; + ImmutableList sessionsToNotify = + ImmutableList.copyOf(sessionsAwaitingProvisioning); + // Clear the list before calling onProvisionComplete in case provisioning is re-requested. + sessionsAwaitingProvisioning.clear(); + for (DefaultDrmSession session : sessionsToNotify) { session.onProvisionCompleted(); } - provisioningSessions.clear(); } @Override public void onProvisionError(Exception error) { - for (DefaultDrmSession session : provisioningSessions) { + provisioningSession = null; + ImmutableList sessionsToNotify = + ImmutableList.copyOf(sessionsAwaitingProvisioning); + // Clear the list before calling onProvisionError in case provisioning is re-requested. + sessionsAwaitingProvisioning.clear(); + for (DefaultDrmSession session : sessionsToNotify) { session.onProvisionError(error); } - provisioningSessions.clear(); + } + + public void onSessionFullyReleased(DefaultDrmSession session) { + sessionsAwaitingProvisioning.remove(session); + if (provisioningSession == session) { + provisioningSession = null; + if (!sessionsAwaitingProvisioning.isEmpty()) { + // Other sessions were waiting for the released session to complete a provision operation. + // We need to have one of those sessions perform the provision operation instead. + provisioningSession = sessionsAwaitingProvisioning.iterator().next(); + provisioningSession.provision(); + } + } } } @@ -902,12 +928,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager { if (noMultiSessionDrmSession == session) { noMultiSessionDrmSession = null; } - if (provisioningSessions.size() > 1 && provisioningSessions.get(0) == session) { - // Other sessions were waiting for the released session to complete a provision operation. - // We need to have one of those sessions perform the provision operation instead. - provisioningSessions.get(1).provision(); - } - provisioningSessions.remove(session); + provisioningManagerImpl.onSessionFullyReleased(session); if (sessionKeepaliveMs != C.TIME_UNSET) { checkNotNull(playbackHandler).removeCallbacksAndMessages(session); keepaliveSessions.remove(session); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java index cc3c5f0bd7..0b9f560546 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java @@ -563,6 +563,69 @@ public class DefaultDrmSessionManagerTest { .containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE); } + @Test + public void deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried() { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + + DefaultDrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider( + DRM_SCHEME_UUID, + uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build()) + .build(/* mediaDrmCallback= */ licenseServer); + drmSessionManager.prepare(); + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + // Confirm the device isn't provisioned (otherwise state would be OPENED) + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING); + waitForOpenedWithKeys(drmSession); + + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + assertThat(drmSession.queryKeyStatus()) + .containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE); + } + + @Test + public void provisioningUndoneWhileManagerIsActive_deviceReprovisioned() { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + + FakeExoMediaDrm mediaDrm = new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build(); + DefaultDrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(mediaDrm)) + .setSessionKeepaliveMs(C.TIME_UNSET) + .build(/* mediaDrmCallback= */ licenseServer); + drmSessionManager.prepare(); + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + // Confirm the device isn't provisioned (otherwise state would be OPENED) + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING); + waitForOpenedWithKeys(drmSession); + drmSession.release(/* eventDispatcher= */ null); + + mediaDrm.resetProvisioning(); + + drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + // Confirm the device isn't provisioned (otherwise state would be OPENED) + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING); + waitForOpenedWithKeys(drmSession); + } + @Test public void managerNotPrepared_acquireSessionAndPreacquireSessionFail() throws Exception { FakeExoMediaDrm.LicenseServer licenseServer = diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java index 608347c569..e48307d7ee 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java @@ -78,8 +78,8 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { * to be provisioned. * *

An unprovisioned {@link FakeExoMediaDrm} will throw {@link NotProvisionedException} from - * {@link FakeExoMediaDrm#openSession()} until enough valid provisioning responses are passed to - * {@link FakeExoMediaDrm#provideProvisionResponse(byte[])}. + * methods that declare it until enough valid provisioning responses are passed to {@link + * FakeExoMediaDrm#provideProvisionResponse(byte[])}. * *

Defaults to 0 (i.e. device is already provisioned). */ @@ -182,9 +182,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public byte[] openSession() throws MediaDrmException { Assertions.checkState(referenceCount > 0); - if (provisionsReceived < provisionsRequired) { - throw new NotProvisionedException("Not provisioned."); - } + assertProvisioned(); if (openSessionIds.size() >= maxConcurrentSessions) { throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions); } @@ -218,6 +216,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { throw new UnsupportedOperationException("Offline key requests are not supported."); } Assertions.checkArgument(keyType == KEY_TYPE_STREAMING, "Unrecognised keyType: " + keyType); + assertProvisioned(); Assertions.checkState(openSessionIds.contains(toByteList(scope))); Assertions.checkNotNull(schemeDatas); KeyRequestData requestData = @@ -238,6 +237,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { public byte[] provideKeyResponse(byte[] scope, byte[] response) throws NotProvisionedException, DeniedByServerException { Assertions.checkState(referenceCount > 0); + assertProvisioned(); List responseAsList = Bytes.asList(response); if (responseAsList.equals(VALID_KEY_RESPONSE)) { sessionIdsWithValidKeys.add(Bytes.asList(scope)); @@ -365,6 +365,21 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { } } + /** + * Resets the provisioning state of this instance, so it requires {@link + * Builder#setProvisionsRequired(int) provisionsRequired} (possibly zero) provision operations + * before it's operational again. + */ + public void resetProvisioning() { + provisionsReceived = 0; + } + + private void assertProvisioned() throws NotProvisionedException { + if (provisionsReceived < provisionsRequired) { + throw new NotProvisionedException("Not provisioned."); + } + } + private static ImmutableList toByteList(byte[] byteArray) { return ImmutableList.copyOf(Bytes.asList(byteArray)); } From 60bbe64ab384c5f8aa9667a1725ddb6967ebb51d Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 21 Jun 2021 10:23:06 +0100 Subject: [PATCH 14/48] Simplify FileDataSourceContractTest #minor-release PiperOrigin-RevId: 380531272 --- .../upstream/FileDataSourceContractTest.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java index 671235738c..940f4251a2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/FileDataSourceContractTest.java @@ -21,7 +21,6 @@ import com.google.android.exoplayer2.testutil.DataSourceContractTest; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.common.collect.ImmutableList; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import org.junit.Before; @@ -37,21 +36,19 @@ public class FileDataSourceContractTest extends DataSourceContractTest { @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); - private Uri simpleUri; + private Uri uri; @Before - public void writeFiles() throws Exception { - simpleUri = writeFile(DATA); + public void writeFile() throws Exception { + File file = tempFolder.newFile(); + Files.write(Paths.get(file.getAbsolutePath()), DATA); + uri = Uri.fromFile(file); } @Override protected ImmutableList getTestResources() { return ImmutableList.of( - new TestResource.Builder() - .setName("simple") - .setUri(simpleUri) - .setExpectedBytes(DATA) - .build()); + new TestResource.Builder().setName("simple").setUri(uri).setExpectedBytes(DATA).build()); } @Override @@ -63,10 +60,4 @@ public class FileDataSourceContractTest extends DataSourceContractTest { protected DataSource createDataSource() { return new FileDataSource(); } - - private Uri writeFile(byte[] data) throws IOException { - File file = tempFolder.newFile(); - Files.write(Paths.get(file.getAbsolutePath()), data); - return Uri.fromFile(file); - } } From e2040a58935d4463a53fdaf4c952109cefe37cc2 Mon Sep 17 00:00:00 2001 From: claincly Date: Mon, 21 Jun 2021 12:17:45 +0100 Subject: [PATCH 15/48] Use a HashMap like behaviour in parsing SDP. Some server will wrongly insert duplicated attributes. We used to treat this as a unrecoverable error, but it is better to treat the duplicated attributes in an "over-writable" fashion like HashMaps. Issue: #9080, Issue: #9014 PiperOrigin-RevId: 380547079 --- .../source/rtsp/MediaDescription.java | 10 +++---- .../source/rtsp/SessionDescription.java | 9 ++++--- .../source/rtsp/SessionDescriptionTest.java | 26 ++++++++++++++----- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java index e4467d395e..6bda38e8d4 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableMap; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; /** Represents one media description section in a SDP message. */ /* package */ final class MediaDescription { @@ -106,7 +107,7 @@ import java.lang.annotation.RetentionPolicy; private final int port; private final String transportProtocol; private final int payloadType; - private final ImmutableMap.Builder attributesBuilder; + private final HashMap attributes; private int bitrate; @Nullable private String mediaTitle; @@ -126,7 +127,7 @@ import java.lang.annotation.RetentionPolicy; this.port = port; this.transportProtocol = transportProtocol; this.payloadType = payloadType; - attributesBuilder = new ImmutableMap.Builder<>(); + attributes = new HashMap<>(); bitrate = Format.NO_VALUE; } @@ -184,7 +185,7 @@ import java.lang.annotation.RetentionPolicy; * @return This builder. */ public Builder addAttribute(String attributeName, String attributeValue) { - attributesBuilder.put(attributeName, attributeValue); + attributes.put(attributeName, attributeValue); return this; } @@ -195,13 +196,12 @@ import java.lang.annotation.RetentionPolicy; * cannot be parsed. */ public MediaDescription build() { - ImmutableMap attributes = attributesBuilder.build(); try { // rtpmap attribute is mandatory in RTSP (RFC2326 Section C.1.3). checkState(attributes.containsKey(ATTR_RTPMAP)); RtpMapAttribute rtpMapAttribute = RtpMapAttribute.parse(castNonNull(attributes.get(ATTR_RTPMAP))); - return new MediaDescription(this, attributes, rtpMapAttribute); + return new MediaDescription(this, ImmutableMap.copyOf(attributes), rtpMapAttribute); } catch (ParserException e) { throw new IllegalStateException(e); } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/SessionDescription.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/SessionDescription.java index 4ac138f554..770709b1b1 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/SessionDescription.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/SessionDescription.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import java.util.HashMap; /** * Records all the information in a SDP message. @@ -35,7 +36,7 @@ import com.google.common.collect.ImmutableMap; /** Builder class for {@link SessionDescription}. */ public static final class Builder { - private final ImmutableMap.Builder attributesBuilder; + private final HashMap attributes; private final ImmutableList.Builder mediaDescriptionListBuilder; private int bitrate; @Nullable private String sessionName; @@ -50,7 +51,7 @@ import com.google.common.collect.ImmutableMap; /** Creates a new instance. */ public Builder() { - attributesBuilder = new ImmutableMap.Builder<>(); + attributes = new HashMap<>(); mediaDescriptionListBuilder = new ImmutableList.Builder<>(); bitrate = Format.NO_VALUE; } @@ -179,7 +180,7 @@ import com.google.common.collect.ImmutableMap; * @return This builder. */ public Builder addAttribute(String attributeName, String attributeValue) { - attributesBuilder.put(attributeName, attributeValue); + attributes.put(attributeName, attributeValue); return this; } @@ -259,7 +260,7 @@ import com.google.common.collect.ImmutableMap; /** Creates a new instance. */ private SessionDescription(Builder builder) { - this.attributes = builder.attributesBuilder.build(); + this.attributes = ImmutableMap.copyOf(builder.attributes); this.mediaDescriptionList = builder.mediaDescriptionListBuilder.build(); this.sessionName = castNonNull(builder.sessionName); this.origin = castNonNull(builder.origin); diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/SessionDescriptionTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/SessionDescriptionTest.java index 47cd40e714..d71976148d 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/SessionDescriptionTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/SessionDescriptionTest.java @@ -30,7 +30,6 @@ import static org.junit.Assert.assertThrows; import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.ParserException; import org.junit.Test; import org.junit.runner.RunWith; @@ -151,32 +150,45 @@ public class SessionDescriptionTest { } @Test - public void parse_sdpStringWithDuplicatedMediaAttribute_throwsParserException() { + public void parse_sdpStringWithDuplicatedMediaAttribute_recordsTheMostRecentValue() + throws Exception { String testMediaSdpInfo = "v=0\r\n" + "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n" + "s=SDP Seminar\r\n" + + "t=0 0\r\n" + "i=A Seminar on the session description protocol\r\n" + "m=audio 3456 RTP/AVP 0\r\n" - + "a=control:audio\r\n" + + "a=control:video\r\n" + + "a=rtpmap:97 AC3/44100\r\n" + // Duplicate attribute. + "a=control:audio\r\n"; - assertThrows(ParserException.class, () -> SessionDescriptionParser.parse(testMediaSdpInfo)); + SessionDescription sessionDescription = SessionDescriptionParser.parse(testMediaSdpInfo); + + assertThat(sessionDescription.mediaDescriptionList.get(0).attributes) + .containsEntry(ATTR_CONTROL, "audio"); } @Test - public void parse_sdpStringWithDuplicatedSessionAttribute_throwsParserException() { + public void parse_sdpStringWithDuplicatedSessionAttribute_recordsTheMostRecentValue() + throws Exception { String testMediaSdpInfo = "v=0\r\n" + "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n" + "s=SDP Seminar\r\n" + + "t=0 0\r\n" + "a=control:*\r\n" - + "a=control:*\r\n" + // Duplicate attribute. + + "a=control:session1\r\n" + "i=A Seminar on the session description protocol\r\n" + "m=audio 3456 RTP/AVP 0\r\n" + + "a=rtpmap:97 AC3/44100\r\n" + "a=control:audio\r\n"; - assertThrows(ParserException.class, () -> SessionDescriptionParser.parse(testMediaSdpInfo)); + SessionDescription sessionDescription = SessionDescriptionParser.parse(testMediaSdpInfo); + + assertThat(sessionDescription.attributes).containsEntry(ATTR_CONTROL, "session1"); } @Test From de16dea00615162a244a07f14f46c68a57d8da7e Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 21 Jun 2021 15:05:27 +0100 Subject: [PATCH 16/48] Re-word the DataSourceContractTest javadoc This softens the language around suppressing individual test methods. There are some legitimate cases where this is needed, e.g. ByteArrayDataSourceContractTest has to suppress all the tests related to non-existent resources because it's not possible to simulate that case. #minor-release PiperOrigin-RevId: 380570017 --- .../exoplayer2/testutil/DataSourceContractTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java index 857b22a5e7..4bce4a11ab 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java @@ -53,16 +53,14 @@ import org.mockito.Mockito; /** * A collection of contract tests for {@link DataSource} implementations. * - *

All these tests should pass for all implementations - behaviour specific to only a subset of - * implementations should be tested elsewhere. - * *

Subclasses should only include the logic necessary to construct the DataSource and allow it to * successfully read data. They shouldn't include any new {@link Test @Test} methods - * implementation-specific tests should be in a separate class. * - *

If one of these tests fails for a particular {@link DataSource} implementation, that's a bug - * in the implementation. The test should be overridden in the subclass and annotated {@link - * Ignore}, with a link to an issue to track fixing the implementation and un-ignoring the test. + *

Most implementations should pass all these tests. If necessary, subclasses can disable tests + * by overriding the {@link Test @Test} method with a no-op implementation. It's recommended (but + * not required) to also annotate this {@link Ignore @Ignore} so that JUnit correclty reports the + * test as skipped/ignored instead of passing. */ @RequiresApi(19) public abstract class DataSourceContractTest { From 8287b2529b2e566e38d32b47eb1f6c06ed1e4bb6 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 21 Jun 2021 20:38:25 +0100 Subject: [PATCH 17/48] Workaround for focus issues on API levels less than 26 Issue: #9061 PiperOrigin-RevId: 380640601 --- RELEASENOTES.md | 3 +++ .../android/exoplayer2/ui/StyledPlayerControlView.java | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cb244b6c94..39fa946b80 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -32,6 +32,9 @@ * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on Android 12. + * Fix focusability of `StyledPlayerView` and `StyledPlayerControlView` + popup menus on API levels prior to 26 + ([#9061](https://github.com/google/ExoPlayer/issues/9061)). ### 2.14.1 (2021-06-11) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index c2e7446e8a..d373a1b77e 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -1945,6 +1945,10 @@ public class StyledPlayerControlView extends FrameLayout { public SettingViewHolder(View itemView) { super(itemView); + if (Util.SDK_INT < 26) { + // Workaround for https://github.com/google/ExoPlayer/issues/9061. + itemView.setFocusable(true); + } mainTextView = itemView.findViewById(R.id.exo_main_text); subTextView = itemView.findViewById(R.id.exo_sub_text); iconView = itemView.findViewById(R.id.exo_icon); @@ -2271,6 +2275,10 @@ public class StyledPlayerControlView extends FrameLayout { public SubSettingViewHolder(View itemView) { super(itemView); + if (Util.SDK_INT < 26) { + // Workaround for https://github.com/google/ExoPlayer/issues/9061. + itemView.setFocusable(true); + } textView = itemView.findViewById(R.id.exo_text); checkView = itemView.findViewById(R.id.exo_check); } From 93f4e5ff852acede55a83451ab537c28eeed5864 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 21 Jun 2021 21:47:17 +0100 Subject: [PATCH 18/48] Specify a root when inflating child views #minor-release PiperOrigin-RevId: 380655806 --- .../exoplayer2/ui/StyledPlayerControlView.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index d373a1b77e..886bfd9f21 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -677,7 +677,8 @@ public class StyledPlayerControlView extends FrameLayout { settingsWindowMargin = resources.getDimensionPixelSize(R.dimen.exo_settings_offset); settingsView = (RecyclerView) - LayoutInflater.from(context).inflate(R.layout.exo_styled_settings_list, null); + LayoutInflater.from(context) + .inflate(R.layout.exo_styled_settings_list, /* root= */ null); settingsView.setAdapter(settingsAdapter); settingsView.setLayoutManager(new LinearLayoutManager(getContext())); settingsWindow = @@ -1899,9 +1900,10 @@ public class StyledPlayerControlView extends FrameLayout { } @Override - public SettingViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + public SettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = - LayoutInflater.from(getContext()).inflate(R.layout.exo_styled_settings_list_item, null); + LayoutInflater.from(getContext()) + .inflate(R.layout.exo_styled_settings_list_item, parent, /* attachToRoot= */ false); return new SettingViewHolder(v); } @@ -1989,7 +1991,8 @@ public class StyledPlayerControlView extends FrameLayout { public SubSettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(getContext()) - .inflate(R.layout.exo_styled_sub_settings_list_item, null); + .inflate( + R.layout.exo_styled_sub_settings_list_item, parent, /* attachToRoot= */ false); return new SubSettingViewHolder(v); } @@ -2203,7 +2206,8 @@ public class StyledPlayerControlView extends FrameLayout { public SubSettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(getContext()) - .inflate(R.layout.exo_styled_sub_settings_list_item, null); + .inflate( + R.layout.exo_styled_sub_settings_list_item, parent, /* attachToRoot= */ false); return new SubSettingViewHolder(v); } From 79f03dfba314c455520033a229c7f36199bcc681 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 21 Jun 2021 23:31:22 +0100 Subject: [PATCH 19/48] Fix DefaultTimeBar glitches The glitches were introduced in: https://github.com/google/ExoPlayer/commit/6c31e34528 The problem is that Listener.onEvents is called in a later looper iteration than the listener methods that were previously used. This created a gap on the main thread between the UI component dispatching a seek operation to the player, and onEvents being called to update the progress bar's position. At the start of this gap the progress bar is rendering the new position, but its position member variable is still set to the old position. If the progress bar is re-drawn by another message on the main thread within the gap, it will briefly show the old position until onEvents is called. There are multiple possible fixes to this, and the best one is probably to modify ListenerSet to remove the gap. That's high risk though, so for now we fix the flicker by always updating the progress immediately after the seek is dispatched, in addition to when onEvents is called. Issue: #9049 PiperOrigin-RevId: 380678388 --- RELEASENOTES.md | 2 ++ .../google/android/exoplayer2/ui/PlayerControlView.java | 8 ++------ .../android/exoplayer2/ui/StyledPlayerControlView.java | 8 ++------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 39fa946b80..9c7a68cec6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -35,6 +35,8 @@ * Fix focusability of `StyledPlayerView` and `StyledPlayerControlView` popup menus on API levels prior to 26 ([#9061](https://github.com/google/ExoPlayer/issues/9061)). + * Fix progress bar flickering immediately after the user seeks + ([#9049](https://github.com/google/ExoPlayer/pull/9049)). ### 2.14.1 (2021-06-11) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java index 1499aa556a..e4a88c083e 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java @@ -1155,12 +1155,8 @@ public class PlayerControlView extends FrameLayout { } else { windowIndex = player.getCurrentWindowIndex(); } - boolean dispatched = seekTo(player, windowIndex, positionMs); - if (!dispatched) { - // The seek wasn't dispatched then the progress bar scrubber will be in the wrong position. - // Trigger a progress update to snap it back. - updateProgress(); - } + seekTo(player, windowIndex, positionMs); + updateProgress(); } private boolean seekTo(Player player, int windowIndex, long positionMs) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index 886bfd9f21..86086dd90b 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -1534,12 +1534,8 @@ public class StyledPlayerControlView extends FrameLayout { } else { windowIndex = player.getCurrentWindowIndex(); } - boolean dispatched = seekTo(player, windowIndex, positionMs); - if (!dispatched) { - // The seek wasn't dispatched then the progress bar scrubber will be in the wrong position. - // Trigger a progress update to snap it back. - updateProgress(); - } + seekTo(player, windowIndex, positionMs); + updateProgress(); } private boolean seekTo(Player player, int windowIndex, long positionMs) { From 5b8be42fa1c476c36b66adb6be20165188b2ae99 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 21 Jun 2021 23:42:35 +0100 Subject: [PATCH 20/48] HLS: Fix issue where a live event doesn't transition to STATE_ENDED The problem occurs when the primary media playlist URL switches from one whose latest snapshot has not yet got the ended tag, to one whose latest snapshot already has the ended tag. In this case: - We trigger a redundant load of the ended playlist. - When the redundant load completes, MediaPlaylistBundle.processLoadedPlaylist detects that the playlist is unchanged from the one it already has, and so doesn't call onPlaylistUpdated. - PrimaryPlaylistListener.onPrimaryPlaylistRefreshed is never called with the new primary. Hence the externally visible primary is still the one that hasn't ended. HlsMediaSource therefore thinks the event hasn't ended, which in turn prevents the player from transitioning to the ended state. This commit detects when the new primary already has the ended tag. In this case, we call onPrimaryPlaylistRefreshed directly and remove the unnecessary playlist load. Issue: #9067 #minor-release PiperOrigin-RevId: 380680532 --- RELEASENOTES.md | 3 +++ .../hls/playlist/DefaultHlsPlaylistTracker.java | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9c7a68cec6..2c1cfde1ea 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,6 +20,9 @@ ([#4083](https://github.com/google/ExoPlayer/issues/4083)). Such content is malformed and should be re-encoded. * HLS: + * Fix issue where playback of a live event could become stuck rather than + transitioning to `STATE_ENDED` when the event ends + ([#9067](https://github.com/google/ExoPlayer/issues/9067)). * Fix issue where a new initialization segment, as specified by an `EXT-X-MAP` tag in a media playlist, would not be loaded when encountered during playback diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index 2a69f5c6af..9ad1cb5934 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -339,9 +339,16 @@ public final class DefaultHlsPlaylistTracker return; } primaryMediaPlaylistUrl = url; - playlistBundles - .get(primaryMediaPlaylistUrl) - .loadPlaylistInternal(getRequestUriForPrimaryChange(url)); + MediaPlaylistBundle newPrimaryBundle = playlistBundles.get(primaryMediaPlaylistUrl); + @Nullable HlsMediaPlaylist newPrimarySnapshot = newPrimaryBundle.playlistSnapshot; + if (newPrimarySnapshot != null && newPrimarySnapshot.hasEndTag) { + primaryMediaPlaylistSnapshot = newPrimarySnapshot; + primaryPlaylistListener.onPrimaryPlaylistRefreshed(newPrimarySnapshot); + } else { + // The snapshot for the new primary media playlist URL may be stale. Defer updating the + // primary snapshot until after we've refreshed it. + newPrimaryBundle.loadPlaylistInternal(getRequestUriForPrimaryChange(url)); + } } private Uri getRequestUriForPrimaryChange(Uri newPrimaryPlaylistUri) { From 90cd2a213152b845bc994ccc5a6728dc624c9d5b Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 22 Jun 2021 09:33:38 +0100 Subject: [PATCH 21/48] Fix expansion of items within StyledPlayerView popup Issue: #9086 PiperOrigin-RevId: 380756562 --- RELEASENOTES.md | 3 +++ library/ui/src/main/res/layout/exo_styled_settings_list.xml | 3 +-- .../ui/src/main/res/layout/exo_styled_settings_list_item.xml | 2 +- .../src/main/res/layout/exo_styled_sub_settings_list_item.xml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2c1cfde1ea..eef9fa8337 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -40,6 +40,9 @@ ([#9061](https://github.com/google/ExoPlayer/issues/9061)). * Fix progress bar flickering immediately after the user seeks ([#9049](https://github.com/google/ExoPlayer/pull/9049)). + * Fix `StyledPlayerView` and `StyledPlayerControlView` popup menu items + not expanding to occupy the full width of the popup + ([#9086](https://github.com/google/ExoPlayer/issues/9086)). ### 2.14.1 (2021-06-11) diff --git a/library/ui/src/main/res/layout/exo_styled_settings_list.xml b/library/ui/src/main/res/layout/exo_styled_settings_list.xml index 3dea7a4c9c..70b44c8d76 100644 --- a/library/ui/src/main/res/layout/exo_styled_settings_list.xml +++ b/library/ui/src/main/res/layout/exo_styled_settings_list.xml @@ -16,8 +16,7 @@ diff --git a/library/ui/src/main/res/layout/exo_styled_settings_list_item.xml b/library/ui/src/main/res/layout/exo_styled_settings_list_item.xml index 25204ffc7d..c93fc01985 100644 --- a/library/ui/src/main/res/layout/exo_styled_settings_list_item.xml +++ b/library/ui/src/main/res/layout/exo_styled_settings_list_item.xml @@ -14,7 +14,7 @@ limitations under the License. --> Date: Tue, 22 Jun 2021 10:53:44 +0100 Subject: [PATCH 22/48] Amend release note for https://github.com/google/ExoPlayer/commit/46bc49a4f6e8194ddb28e7b17eb775157c3e241d. PiperOrigin-RevId: 380766548 --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eef9fa8337..bdc396160c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -43,6 +43,10 @@ * Fix `StyledPlayerView` and `StyledPlayerControlView` popup menu items not expanding to occupy the full width of the popup ([#9086](https://github.com/google/ExoPlayer/issues/9086)). +* RTSP + * Fix session description (SDP) parsing to use a HashMap-like behaviour + for duplicated attributes. + ([#9014](https://github.com/google/ExoPlayer/issues/9014)). ### 2.14.1 (2021-06-11) From baa9a367e2e8ae4d84f5cdb7a4d07b7256401d3f Mon Sep 17 00:00:00 2001 From: claincly Date: Wed, 23 Jun 2021 09:47:11 +0100 Subject: [PATCH 23/48] Improve timeout handling and allow customizing the timeout. Previously, a SocketTimeourException is used to signal the end of the stream that is caused by "no RTP packets received for a while". However, such signaling is inappropriate under TransferRtpDataChannel, or FakeRtpDataChannel in RtspPlaybackTests. Hence, the signaling of end of stream is changed to use RESULT_END_OF_INPUT. The RtpDataChannel implementations will Still block until a set timeout, but will return a C.RESULT_END_OF_INPUT should a timeout occur, instead of throwing a nested SocketTimeoutException. This also allowed customization of the timeout amount, in RtspMediaSource.Factory PiperOrigin-RevId: 380981534 --- .../source/rtsp/RtpDataChannel.java | 7 ++ .../source/rtsp/RtpDataLoadable.java | 9 ++- .../exoplayer2/source/rtsp/RtpExtractor.java | 6 +- .../source/rtsp/RtspMediaPeriod.java | 66 ++++++++++--------- .../source/rtsp/RtspMediaSource.java | 26 +++++++- .../source/rtsp/TransferRtpDataChannel.java | 22 ++++--- .../rtsp/TransferRtpDataChannelFactory.java | 14 +++- .../rtsp/UdpDataSourceRtpDataChannel.java | 23 +++++-- .../UdpDataSourceRtpDataChannelFactory.java | 21 +++++- .../rtsp/TransferRtpDataChannelTest.java | 41 ++++++------ .../rtsp/UdpDataSourceRtpDataChannelTest.java | 4 +- 11 files changed, 162 insertions(+), 77 deletions(-) diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataChannel.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataChannel.java index 97ddd66199..ab39263102 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataChannel.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataChannel.java @@ -30,9 +30,16 @@ import java.io.IOException; /** * Creates a new {@link RtpDataChannel} instance for RTP data transfer. * + * @param trackId The track ID. * @throws IOException If the data channels failed to open. */ RtpDataChannel createAndOpenDataChannel(int trackId) throws IOException; + + /** Returns a fallback {@code Factory}, {@code null} when there is no fallback available. */ + @Nullable + default Factory createFallbackDataChannelFactory() { + return null; + } } /** Returns the RTSP transport header for this {@link RtpDataChannel} */ diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataLoadable.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataLoadable.java index 573590047b..418b9c2ce6 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataLoadable.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpDataLoadable.java @@ -22,6 +22,7 @@ import android.os.Handler; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.DefaultExtractorInput; +import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.PositionHolder; @@ -153,7 +154,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; extractor.seek(nextRtpTimestamp, pendingSeekPositionUs); pendingSeekPositionUs = C.TIME_UNSET; } - extractor.read(extractorInput, /* seekPosition= */ new PositionHolder()); + + @Extractor.ReadResult + int readResult = extractor.read(extractorInput, /* seekPosition= */ new PositionHolder()); + if (readResult == Extractor.RESULT_END_OF_INPUT) { + // Loading is finished. + break; + } } } finally { Util.closeQuietly(dataChannel); diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpExtractor.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpExtractor.java index 50894a45f5..94c5902cd3 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpExtractor.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpExtractor.java @@ -125,10 +125,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // Reads one RTP packet at a time. int bytesRead = input.read(rtpPacketScratchBuffer.getData(), 0, RtpPacket.MAX_SIZE); - if (bytesRead == RESULT_END_OF_INPUT) { - return RESULT_END_OF_INPUT; + if (bytesRead == C.RESULT_END_OF_INPUT) { + return Extractor.RESULT_END_OF_INPUT; } else if (bytesRead == 0) { - return RESULT_CONTINUE; + return Extractor.RESULT_CONTINUE; } rtpPacketScratchBuffer.setPosition(0); diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java index 19ce014c2b..db72bc0637 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java @@ -48,13 +48,11 @@ import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Loader; -import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; import com.google.android.exoplayer2.upstream.Loader.Loadable; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.net.BindException; -import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -103,6 +101,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param rtpDataChannelFactory A {@link RtpDataChannel.Factory} for {@link RtpDataChannel}. * @param uri The RTSP playback {@link Uri}. * @param listener A {@link Listener} to receive session information updates. + * @param userAgent The user agent. */ public RtspMediaPeriod( Allocator allocator, @@ -432,7 +431,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void onLoadCompleted( - RtpDataLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {} + RtpDataLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) { + // TODO(b/172331505) Allow for retry when loading is not ending. + if (getBufferedPositionUs() == 0) { + if (!isUsingRtpTcp) { + // Retry playback with TCP if no sample has been received so far, and we are not already + // using TCP. Retrying will setup new loadables, so will not retry with the current + // loadables. + retryWithRtpTcp(); + isUsingRtpTcp = true; + } + return; + } + + // Cancel the loader wrapper associated with the completed loadable. + for (int i = 0; i < rtspLoaderWrappers.size(); i++) { + RtspLoaderWrapper loaderWrapper = rtspLoaderWrappers.get(i); + if (loaderWrapper.loadInfo.loadable == loadable) { + loaderWrapper.cancelLoad(); + break; + } + } + } @Override public void onLoadCanceled( @@ -458,9 +478,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (!prepared) { preparationError = error; } else { - if (error.getCause() instanceof SocketTimeoutException) { - return handleSocketTimeout(loadable); - } else if (error.getCause() instanceof BindException) { + if (error.getCause() instanceof BindException) { // Allow for retry on RTP port open failure by catching BindException. Two ports are // opened for each RTP stream, the first port number is auto assigned by the system, while // the second is manually selected. It is thus possible that the second port fails to @@ -535,30 +553,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; playbackException = error; } - /** Handles the {@link Loadable} whose {@link RtpDataChannel} timed out. */ - private LoadErrorAction handleSocketTimeout(RtpDataLoadable loadable) { - // TODO(b/172331505) Allow for retry when loading is not ending. - if (getBufferedPositionUs() == 0) { - if (!isUsingRtpTcp) { - // Retry playback with TCP if no sample has been received so far, and we are not already - // using TCP. Retrying will setup new loadables, so will not retry with the current - // loadables. - retryWithRtpTcp(); - isUsingRtpTcp = true; - } - return Loader.DONT_RETRY; - } - - for (int i = 0; i < rtspLoaderWrappers.size(); i++) { - RtspLoaderWrapper loaderWrapper = rtspLoaderWrappers.get(i); - if (loaderWrapper.loadInfo.loadable == loadable) { - loaderWrapper.cancelLoad(); - break; - } - } - return Loader.DONT_RETRY; - } - @Override public void onSessionTimelineUpdated( RtspSessionTiming timing, ImmutableList tracks) { @@ -582,7 +576,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private void retryWithRtpTcp() { rtspClient.retryWithRtpTcp(); - RtpDataChannel.Factory rtpDataChannelFactory = new TransferRtpDataChannelFactory(); + @Nullable + RtpDataChannel.Factory fallbackRtpDataChannelFactory = + rtpDataChannelFactory.createFallbackDataChannelFactory(); + if (fallbackRtpDataChannelFactory == null) { + playbackException = + new RtspPlaybackException("No fallback data channel factory for TCP retry"); + return; + } + ArrayList newLoaderWrappers = new ArrayList<>(rtspLoaderWrappers.size()); ArrayList newSelectedLoadInfos = new ArrayList<>(selectedLoadInfos.size()); @@ -593,7 +595,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (!loaderWrapper.canceled) { RtspLoaderWrapper newLoaderWrapper = new RtspLoaderWrapper( - loaderWrapper.loadInfo.mediaTrack, /* trackId= */ i, rtpDataChannelFactory); + loaderWrapper.loadInfo.mediaTrack, /* trackId= */ i, fallbackRtpDataChannelFactory); newLoaderWrappers.add(newLoaderWrapper); newLoaderWrapper.startLoading(); if (selectedLoadInfos.contains(loaderWrapper.loadInfo)) { diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java index a038b53315..b6799e8b67 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java @@ -16,9 +16,11 @@ package com.google.android.exoplayer2.source.rtsp; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.net.Uri; +import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; @@ -45,6 +47,9 @@ public final class RtspMediaSource extends BaseMediaSource { ExoPlayerLibraryInfo.registerModule("goog.exo.rtsp"); } + /** The default value for {@link Factory#setTimeoutMs}. */ + public static final long DEFAULT_TIMEOUT_MS = 8000; + /** * Factory for {@link RtspMediaSource} * @@ -60,10 +65,12 @@ public final class RtspMediaSource extends BaseMediaSource { */ public static final class Factory implements MediaSourceFactory { + private long timeoutMs; private String userAgent; private boolean forceUseRtpTcp; public Factory() { + timeoutMs = DEFAULT_TIMEOUT_MS; userAgent = ExoPlayerLibraryInfo.VERSION_SLASHY; } @@ -94,6 +101,21 @@ public final class RtspMediaSource extends BaseMediaSource { return this; } + /** + * Sets the timeout in milliseconds, the default value is {@link #DEFAULT_TIMEOUT_MS}. + * + *

A positive number of milliseconds to wait before lack of received RTP packets is treated + * as the end of input. + * + * @param timeoutMs The timeout measured in milliseconds. + * @return This Factory, for convenience. + */ + public Factory setTimeoutMs(@IntRange(from = 1) long timeoutMs) { + checkArgument(timeoutMs > 0); + this.timeoutMs = timeoutMs; + return this; + } + /** Does nothing. {@link RtspMediaSource} does not support DRM. */ @Override public Factory setDrmSessionManagerProvider( @@ -161,8 +183,8 @@ public final class RtspMediaSource extends BaseMediaSource { return new RtspMediaSource( mediaItem, forceUseRtpTcp - ? new TransferRtpDataChannelFactory() - : new UdpDataSourceRtpDataChannelFactory(), + ? new TransferRtpDataChannelFactory(timeoutMs) + : new UdpDataSourceRtpDataChannelFactory(timeoutMs), userAgent); } } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannel.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannel.java index 9a1d3b4e25..bd036d9f1c 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannel.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannel.java @@ -26,8 +26,6 @@ import com.google.android.exoplayer2.source.rtsp.RtspMessageChannel.InterleavedB import com.google.android.exoplayer2.upstream.BaseDataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; -import java.io.IOException; -import java.net.SocketTimeoutException; import java.util.Arrays; import java.util.concurrent.LinkedBlockingQueue; @@ -37,16 +35,22 @@ import java.util.concurrent.LinkedBlockingQueue; private static final String DEFAULT_TCP_TRANSPORT_FORMAT = "RTP/AVP/TCP;unicast;interleaved=%d-%d"; - private static final long TIMEOUT_MS = 8_000; private final LinkedBlockingQueue packetQueue; + private final long pollTimeoutMs; private byte[] unreadData; private int channelNumber; - /** Creates a new instance. */ - public TransferRtpDataChannel() { + /** + * Creates a new instance. + * + * @param pollTimeoutMs The number of milliseconds which {@link #read} waits for a packet to be + * available. After the time has expired, {@link C#RESULT_END_OF_INPUT} is returned. + */ + public TransferRtpDataChannel(long pollTimeoutMs) { super(/* isNetwork= */ true); + this.pollTimeoutMs = pollTimeoutMs; packetQueue = new LinkedBlockingQueue<>(); unreadData = new byte[0]; channelNumber = C.INDEX_UNSET; @@ -84,7 +88,7 @@ import java.util.concurrent.LinkedBlockingQueue; } @Override - public int read(byte[] target, int offset, int length) throws IOException { + public int read(byte[] target, int offset, int length) { if (length == 0) { return 0; } @@ -101,11 +105,9 @@ import java.util.concurrent.LinkedBlockingQueue; @Nullable byte[] data; try { - // TODO(internal b/172331505) Consider move the receiving timeout logic to an upper level - // (maybe RtspClient). There is no actual socket receiving here. - data = packetQueue.poll(TIMEOUT_MS, MILLISECONDS); + data = packetQueue.poll(pollTimeoutMs, MILLISECONDS); if (data == null) { - throw new IOException(new SocketTimeoutException()); + return C.RESULT_END_OF_INPUT; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelFactory.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelFactory.java index 31860a8fd6..e3829aab58 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelFactory.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelFactory.java @@ -20,9 +20,21 @@ package com.google.android.exoplayer2.source.rtsp; private static final int INTERLEAVED_CHANNELS_PER_TRACK = 2; + private final long timeoutMs; + + /** + * Creates a new instance. + * + * @param timeoutMs A positive number of milliseconds to wait before lack of received RTP packets + * is treated as the end of input. + */ + public TransferRtpDataChannelFactory(long timeoutMs) { + this.timeoutMs = timeoutMs; + } + @Override public RtpDataChannel createAndOpenDataChannel(int trackId) { - TransferRtpDataChannel dataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel dataChannel = new TransferRtpDataChannel(timeoutMs); dataChannel.open(RtpUtils.getIncomingRtpDataSpec(trackId * INTERLEAVED_CHANNELS_PER_TRACK)); return dataChannel; } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannel.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannel.java index afa870a392..95bf5027ca 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannel.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannel.java @@ -25,7 +25,9 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.UdpDataSource; import com.google.android.exoplayer2.util.Util; +import com.google.common.primitives.Ints; import java.io.IOException; +import java.net.SocketTimeoutException; /** An {@link RtpDataChannel} for UDP transport. */ /* package */ final class UdpDataSourceRtpDataChannel implements RtpDataChannel { @@ -37,9 +39,14 @@ import java.io.IOException; /** The associated RTCP channel; {@code null} if the current channel is an RTCP channel. */ @Nullable private UdpDataSourceRtpDataChannel rtcpChannel; - /** Creates a new instance. */ - public UdpDataSourceRtpDataChannel() { - dataSource = new UdpDataSource(); + /** + * Creates a new instance. + * + * @param socketTimeoutMs The timeout for {@link #read} in milliseconds. + */ + public UdpDataSourceRtpDataChannel(long socketTimeoutMs) { + dataSource = + new UdpDataSource(UdpDataSource.DEFAULT_MAX_PACKET_SIZE, Ints.checkedCast(socketTimeoutMs)); } @Override @@ -88,7 +95,15 @@ import java.io.IOException; @Override public int read(byte[] target, int offset, int length) throws IOException { - return dataSource.read(target, offset, length); + try { + return dataSource.read(target, offset, length); + } catch (UdpDataSource.UdpDataSourceException e) { + if (e.getCause() instanceof SocketTimeoutException) { + return C.RESULT_END_OF_INPUT; + } else { + throw e; + } + } } public void setRtcpChannel(UdpDataSourceRtpDataChannel rtcpChannel) { diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelFactory.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelFactory.java index ccb201dbe9..b59753ea12 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelFactory.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelFactory.java @@ -21,10 +21,22 @@ import java.io.IOException; /** Factory for {@link UdpDataSourceRtpDataChannel}. */ /* package */ final class UdpDataSourceRtpDataChannelFactory implements RtpDataChannel.Factory { + private final long socketTimeoutMs; + + /** + * Creates a new instance. + * + * @param socketTimeoutMs A positive number of milliseconds to wait before lack of received RTP + * packets is treated as the end of input. + */ + public UdpDataSourceRtpDataChannelFactory(long socketTimeoutMs) { + this.socketTimeoutMs = socketTimeoutMs; + } + @Override public RtpDataChannel createAndOpenDataChannel(int trackId) throws IOException { - UdpDataSourceRtpDataChannel firstChannel = new UdpDataSourceRtpDataChannel(); - UdpDataSourceRtpDataChannel secondChannel = new UdpDataSourceRtpDataChannel(); + UdpDataSourceRtpDataChannel firstChannel = new UdpDataSourceRtpDataChannel(socketTimeoutMs); + UdpDataSourceRtpDataChannel secondChannel = new UdpDataSourceRtpDataChannel(socketTimeoutMs); try { // From RFC3550 Section 11: "For UDP and similar protocols, RTP SHOULD use an even destination @@ -53,4 +65,9 @@ import java.io.IOException; throw e; } } + + @Override + public RtpDataChannel.Factory createFallbackDataChannelFactory() { + return new TransferRtpDataChannelFactory(/* timeoutMs= */ socketTimeoutMs); + } } diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelTest.java index 93b88fa023..1f330fdbec 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/TransferRtpDataChannelTest.java @@ -17,11 +17,10 @@ package com.google.android.exoplayer2.source.rtsp; import static com.google.android.exoplayer2.testutil.TestUtil.buildTestData; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; import com.google.common.primitives.Bytes; -import java.io.IOException; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,29 +29,30 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class TransferRtpDataChannelTest { + private static final long POLL_TIMEOUT_MS = 8000; + @Test public void getInterleavedBinaryDataListener_returnsAnInterleavedBinaryDataListener() { - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); assertThat(transferRtpDataChannel.getInterleavedBinaryDataListener()) .isEqualTo(transferRtpDataChannel); } @Test - public void read_withoutReceivingInterleavedData_timesOut() { - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + public void read_withoutReceivingInterleavedData_returnsEndOfInput() { + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); byte[] buffer = new byte[1]; - assertThrows( - IOException.class, - () -> transferRtpDataChannel.read(buffer, /* offset= */ 0, buffer.length)); + assertThat(transferRtpDataChannel.read(buffer, /* offset= */ 0, buffer.length)) + .isEqualTo(C.RESULT_END_OF_INPUT); } @Test - public void read_withLargeEnoughBuffer_reads() throws Exception { + public void read_withLargeEnoughBuffer_reads() { byte[] randomBytes = buildTestData(20); byte[] buffer = new byte[40]; - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); transferRtpDataChannel.onInterleavedBinaryDataReceived(randomBytes); transferRtpDataChannel.read(buffer, /* offset= */ 0, buffer.length); @@ -61,10 +61,10 @@ public class TransferRtpDataChannelTest { } @Test - public void read_withSmallBufferEnoughBuffer_readsThreeTimes() throws Exception { + public void read_withSmallBufferEnoughBuffer_readsThreeTimes() { byte[] randomBytes = buildTestData(20); byte[] buffer = new byte[8]; - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); transferRtpDataChannel.onInterleavedBinaryDataReceived(randomBytes); transferRtpDataChannel.read(buffer, /* offset= */ 0, buffer.length); @@ -77,10 +77,10 @@ public class TransferRtpDataChannelTest { } @Test - public void read_withSmallBuffer_reads() throws Exception { + public void read_withSmallBuffer_reads() { byte[] randomBytes = buildTestData(40); byte[] buffer = new byte[20]; - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); transferRtpDataChannel.onInterleavedBinaryDataReceived(randomBytes); transferRtpDataChannel.read(buffer, /* offset= */ 0, buffer.length); @@ -91,12 +91,12 @@ public class TransferRtpDataChannelTest { } @Test - public void read_withSmallAndModerateBufferAndSubsequentProducerWrite_reads() throws Exception { + public void read_withSmallAndModerateBufferAndSubsequentProducerWrite_reads() { byte[] randomBytes1 = buildTestData(40); byte[] randomBytes2 = buildTestData(40); byte[] smallBuffer = new byte[20]; byte[] bigBuffer = new byte[40]; - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); transferRtpDataChannel.onInterleavedBinaryDataReceived(randomBytes1); transferRtpDataChannel.read(smallBuffer, /* offset= */ 0, smallBuffer.length); @@ -120,13 +120,12 @@ public class TransferRtpDataChannelTest { } @Test - public void read_withSmallAndBigBufferWithPartialReadAndSubsequentProducerWrite_reads() - throws Exception { + public void read_withSmallAndBigBufferWithPartialReadAndSubsequentProducerWrite_reads() { byte[] randomBytes1 = buildTestData(40); byte[] randomBytes2 = buildTestData(40); byte[] smallBuffer = new byte[30]; byte[] bigBuffer = new byte[30]; - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); transferRtpDataChannel.onInterleavedBinaryDataReceived(randomBytes1); transferRtpDataChannel.read(smallBuffer, /* offset= */ 0, smallBuffer.length); @@ -150,12 +149,12 @@ public class TransferRtpDataChannelTest { } @Test - public void read_withSmallAndBigBufferAndSubsequentProducerWrite_reads() throws Exception { + public void read_withSmallAndBigBufferAndSubsequentProducerWrite_reads() { byte[] randomBytes1 = buildTestData(40); byte[] randomBytes2 = buildTestData(40); byte[] smallBuffer = new byte[20]; byte[] bigBuffer = new byte[70]; - TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(); + TransferRtpDataChannel transferRtpDataChannel = new TransferRtpDataChannel(POLL_TIMEOUT_MS); transferRtpDataChannel.onInterleavedBinaryDataReceived(randomBytes1); transferRtpDataChannel.read(smallBuffer, /* offset= */ 0, smallBuffer.length); diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelTest.java index 1ff2024edd..45980ca8d4 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/UdpDataSourceRtpDataChannelTest.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.rtsp; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.upstream.UdpDataSource; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,7 +28,8 @@ public class UdpDataSourceRtpDataChannelTest { @Test public void getInterleavedBinaryDataListener_returnsNull() { - UdpDataSourceRtpDataChannel udpDataSourceRtpDataChannel = new UdpDataSourceRtpDataChannel(); + UdpDataSourceRtpDataChannel udpDataSourceRtpDataChannel = + new UdpDataSourceRtpDataChannel(UdpDataSource.DEFAULT_SOCKET_TIMEOUT_MILLIS); assertThat(udpDataSourceRtpDataChannel.getInterleavedBinaryDataListener()).isNull(); } From 125a8d3caabfaecc22fae303fea60aeda93b9f64 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 30 Jun 2021 13:41:35 +0100 Subject: [PATCH 24/48] Merge pull request #9119 from chvp:media2-dispatch-previous-next PiperOrigin-RevId: 381833313 --- .../exoplayer2/ext/media2/PlayerWrapper.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java index f4d934bcf9..ea29ccbe53 100644 --- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java +++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java @@ -231,23 +231,11 @@ import java.util.List; } public boolean skipToPreviousPlaylistItem() { - Timeline timeline = player.getCurrentTimeline(); - Assertions.checkState(!timeline.isEmpty()); - int previousWindowIndex = player.getPreviousWindowIndex(); - if (previousWindowIndex != C.INDEX_UNSET) { - return controlDispatcher.dispatchSeekTo(player, previousWindowIndex, C.TIME_UNSET); - } - return false; + return controlDispatcher.dispatchPrevious(player); } public boolean skipToNextPlaylistItem() { - Timeline timeline = player.getCurrentTimeline(); - Assertions.checkState(!timeline.isEmpty()); - int nextWindowIndex = player.getNextWindowIndex(); - if (nextWindowIndex != C.INDEX_UNSET) { - return controlDispatcher.dispatchSeekTo(player, nextWindowIndex, C.TIME_UNSET); - } - return false; + return controlDispatcher.dispatchNext(player); } public boolean skipToPlaylistItem(@IntRange(from = 0) int index) { From ff5694a0b685536635b015ee45d65d23a5d4878e Mon Sep 17 00:00:00 2001 From: claincly Date: Mon, 28 Jun 2021 14:45:58 +0100 Subject: [PATCH 25/48] Amend RTSP dev guide to match the current code status. PiperOrigin-RevId: 381852972 --- .../_page_fragments/supported-formats-rtsp.md | 13 ++++++---- docs/rtsp.md | 25 ++++++++++++++++++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/_page_fragments/supported-formats-rtsp.md b/docs/_page_fragments/supported-formats-rtsp.md index c87dd690ff..93deb9ce82 100644 --- a/docs/_page_fragments/supported-formats-rtsp.md +++ b/docs/_page_fragments/supported-formats-rtsp.md @@ -2,10 +2,13 @@ ExoPlayer supports both live and on demand RTSP. Supported formats and network types are listed below. **Supported formats** -* H264 -* AAC (with ADTS bitstream) -* AC3 +* H264. The SDP media description must include SPS/PPS data in the fmtp + attribute for decoder initialization. +* AAC (with ADTS bitstream). +* AC3. **Supported network types** -* RTP over UDP unicast (multicast is not supported) -* Interleaved RTSP, RTP over RTSP using TCP +* RTP over UDP unicast (multicast is not supported). +* Interleaved RTSP, RTP over RTSP using TCP. + +> Playback of RTP streams is not supported. diff --git a/docs/rtsp.md b/docs/rtsp.md index 17c11048d0..dab6c9cb51 100644 --- a/docs/rtsp.md +++ b/docs/rtsp.md @@ -26,6 +26,13 @@ player.prepare(); {: .language-java} +### Play authentication-enabled RTSP content ### + +ExoPlayer supports playback with RTSP BASIC and DIGEST authentication. To play +protected RTSP content, the `MediaItem`'s URI must be configured with the +authtication info. Specifically, the URI should follow the format +`rtsp://:@`. + ## Using RtspMediaSource ## For more customization options, you can create an `RtspMediaSource` and pass it @@ -45,7 +52,7 @@ player.prepare(); ~~~ {: .language-java} -## Using RTSP behind a NAT ## +## Using RTSP behind a NAT (RTP/TCP support) ## ExoPlayer uses UDP as the default protocol for RTP transport. @@ -55,3 +62,19 @@ necessary UDP port mapping. If ExoPlayer detects there have not been incoming RTP packets for a while and the playback has not started yet, ExoPlayer tears down the current RTSP playback session, and retries playback using RTP-over-RTSP (transmitting RTP packets using the TCP connection opened for RTSP). + +The timeout for retrying with TCP can be customized by calling the method +`RtspMediaSource.Factory.setTimeoutMs()`. For example, if the timeout is set to +four seconds, the player will retry with TCP after four seconds of UDP +inactivity. + +> Setting the timeout also affects the end-of-stream detection logic. That is, +ExoPlayer will report the playback has ended if nothing is received for the +duration of the set timeout. Setting this value too small may lead to pre-mature +stream ending under poor network conditions. + +### Force using RTP/TCP ### +ExoPlayer can also be configured to play with RTP/TCP by default. To do so, +use method `RtspMediaSource.Factory.setForceUseRtpTcp()`. + + From e272e3b1c8722f40f240bba36372cdc3ca74685d Mon Sep 17 00:00:00 2001 From: kimvde Date: Tue, 29 Jun 2021 15:35:02 +0100 Subject: [PATCH 26/48] Improve support for Ogg truncated content Issue:#7608 PiperOrigin-RevId: 382081687 --- RELEASENOTES.md | 2 + .../exoplayer2/extractor/ExtractorUtil.java | 60 +++++++- .../extractor/ogg/DefaultOggSeeker.java | 22 ++- .../exoplayer2/extractor/ogg/OggPacket.java | 12 +- .../extractor/ogg/OggPageHeader.java | 42 ++---- .../extractor/ExtractorUtilTest.java | 141 +++++++++++++++++- 6 files changed, 232 insertions(+), 47 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bdc396160c..c8773b0785 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,8 @@ is set incorrectly ([#4083](https://github.com/google/ExoPlayer/issues/4083)). Such content is malformed and should be re-encoded. + * Improve support for truncated Ogg streams + ([#7608](https://github.com/google/ExoPlayer/issues/7608)). * HLS: * Fix issue where playback of a live event could become stuck rather than transitioning to `STATE_ENDED` when the event ends diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java index 5ce274b11a..3eca5c776b 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java @@ -16,10 +16,11 @@ package com.google.android.exoplayer2.extractor; import com.google.android.exoplayer2.C; +import java.io.EOFException; import java.io.IOException; /** Extractor related utility methods. */ -/* package */ final class ExtractorUtil { +public final class ExtractorUtil { /** * Peeks {@code length} bytes from the input peek position, or all the bytes to the end of the @@ -47,5 +48,62 @@ import java.io.IOException; return totalBytesPeeked; } + /** + * Equivalent to {@link ExtractorInput#readFully(byte[], int, int)} except that it returns {@code + * false} instead of throwing an {@link EOFException} if the end of input is encountered without + * having fully satisfied the read. + */ + public static boolean readFullyQuietly( + ExtractorInput input, byte[] output, int offset, int length) throws IOException { + try { + input.readFully(output, offset, length); + } catch (EOFException e) { + return false; + } + return true; + } + + /** + * Equivalent to {@link ExtractorInput#skipFully(int)} except that it returns {@code false} + * instead of throwing an {@link EOFException} if the end of input is encountered without having + * fully satisfied the skip. + */ + public static boolean skipFullyQuietly(ExtractorInput input, int length) throws IOException { + try { + input.skipFully(length); + } catch (EOFException e) { + return false; + } + return true; + } + + /** + * Peeks data from {@code input}, respecting {@code allowEndOfInput}. Returns true if the peek is + * successful. + * + *

If {@code allowEndOfInput=false} then encountering the end of the input (whether before or + * after reading some data) will throw {@link EOFException}. + * + *

If {@code allowEndOfInput=true} then encountering the end of the input (even after reading + * some data) will return {@code false}. + * + *

This is slightly different to the behaviour of {@link ExtractorInput#peekFully(byte[], int, + * int, boolean)}, where {@code allowEndOfInput=true} only returns false (and suppresses the + * exception) if the end of the input is reached before reading any data. + */ + public static boolean peekFullyQuietly( + ExtractorInput input, byte[] output, int offset, int length, boolean allowEndOfInput) + throws IOException { + try { + return input.peekFully(output, offset, length, /* allowEndOfInput= */ allowEndOfInput); + } catch (EOFException e) { + if (allowEndOfInput) { + return false; + } else { + throw e; + } + } + } + private ExtractorUtil() {} } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java index b7a8039489..29a0f5932a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ogg; +import static com.google.android.exoplayer2.extractor.ExtractorUtil.skipFullyQuietly; + import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; @@ -229,13 +231,21 @@ import java.io.IOException; if (!pageHeader.skipToNextPage(input)) { throw new EOFException(); } - do { - pageHeader.populate(input, /* quiet= */ false); - input.skipFully(pageHeader.headerSize + pageHeader.bodySize); - } while ((pageHeader.type & 0x04) != 0x04 + pageHeader.populate(input, /* quiet= */ false); + input.skipFully(pageHeader.headerSize + pageHeader.bodySize); + long granulePosition = pageHeader.granulePosition; + while ((pageHeader.type & 0x04) != 0x04 && pageHeader.skipToNextPage(input) - && input.getPosition() < payloadEndPosition); - return pageHeader.granulePosition; + && input.getPosition() < payloadEndPosition) { + boolean hasPopulated = pageHeader.populate(input, /* quiet= */ true); + if (!hasPopulated || !skipFullyQuietly(input, pageHeader.headerSize + pageHeader.bodySize)) { + // The input file contains a partial page at the end. Ignore it and return the granule + // position of the last complete page. + return granulePosition; + } + granulePosition = pageHeader.granulePosition; + } + return granulePosition; } private final class OggSeekMap implements SeekMap { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java index 14a5fea607..dcca44b3d8 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ogg; +import static com.google.android.exoplayer2.extractor.ExtractorUtil.readFullyQuietly; +import static com.google.android.exoplayer2.extractor.ExtractorUtil.skipFullyQuietly; import static java.lang.Math.max; import com.google.android.exoplayer2.C; @@ -55,7 +57,7 @@ import java.util.Arrays; * * @param input The {@link ExtractorInput} to read data from. * @return {@code true} if the read was successful. The read fails if the end of the input is - * encountered without reading data. + * encountered without reading the whole packet. * @throws IOException If reading from the input fails. */ public boolean populate(ExtractorInput input) throws IOException { @@ -80,7 +82,9 @@ import java.util.Arrays; bytesToSkip += calculatePacketSize(segmentIndex); segmentIndex += segmentCount; } - input.skipFully(bytesToSkip); + if (!skipFullyQuietly(input, bytesToSkip)) { + return false; + } currentSegmentIndex = segmentIndex; } @@ -88,7 +92,9 @@ import java.util.Arrays; int segmentIndex = currentSegmentIndex + segmentCount; if (size > 0) { packetArray.ensureCapacity(packetArray.limit() + size); - input.readFully(packetArray.getData(), packetArray.limit(), size); + if (!readFullyQuietly(input, packetArray.getData(), packetArray.limit(), size)) { + return false; + } packetArray.setLimit(packetArray.limit() + size); populated = pageHeader.laces[segmentIndex - 1] != 255; } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java index a59eca6c8c..70a1f22f54 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java @@ -15,12 +15,13 @@ */ package com.google.android.exoplayer2.extractor.ogg; +import static com.google.android.exoplayer2.extractor.ExtractorUtil.peekFullyQuietly; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableByteArray; -import java.io.EOFException; import java.io.IOException; /** @@ -106,7 +107,8 @@ import java.io.IOException; Assertions.checkArgument(input.getPosition() == input.getPeekPosition()); scratch.reset(/* limit= */ CAPTURE_PATTERN_SIZE); while ((limit == C.POSITION_UNSET || input.getPosition() + CAPTURE_PATTERN_SIZE < limit) - && peekSafely(input, scratch.getData(), 0, CAPTURE_PATTERN_SIZE, /* quiet= */ true)) { + && peekFullyQuietly( + input, scratch.getData(), 0, CAPTURE_PATTERN_SIZE, /* allowEndOfInput= */ true)) { scratch.setPosition(0); if (scratch.readUnsignedInt() == CAPTURE_PATTERN) { input.resetPeekPosition(); @@ -127,14 +129,13 @@ import java.io.IOException; * @param input The {@link ExtractorInput} to read from. * @param quiet Whether to return {@code false} rather than throwing an exception if the header * cannot be populated. - * @return Whether the read was successful. The read fails if the end of the input is encountered - * without reading data. + * @return Whether the header was entirely populated. * @throws IOException If reading data fails or the stream is invalid. */ public boolean populate(ExtractorInput input, boolean quiet) throws IOException { reset(); scratch.reset(/* limit= */ EMPTY_PAGE_HEADER_SIZE); - if (!peekSafely(input, scratch.getData(), 0, EMPTY_PAGE_HEADER_SIZE, quiet) + if (!peekFullyQuietly(input, scratch.getData(), 0, EMPTY_PAGE_HEADER_SIZE, quiet) || scratch.readUnsignedInt() != CAPTURE_PATTERN) { return false; } @@ -158,7 +159,9 @@ import java.io.IOException; // calculate total size of header including laces scratch.reset(/* limit= */ pageSegmentCount); - input.peekFully(scratch.getData(), 0, pageSegmentCount); + if (!peekFullyQuietly(input, scratch.getData(), 0, pageSegmentCount, quiet)) { + return false; + } for (int i = 0; i < pageSegmentCount; i++) { laces[i] = scratch.readUnsignedByte(); bodySize += laces[i]; @@ -166,31 +169,4 @@ import java.io.IOException; return true; } - - /** - * Peek data from {@code input}, respecting {@code quiet}. Return true if the peek is successful. - * - *

If {@code quiet=false} then encountering the end of the input (whether before or after - * reading some data) will throw {@link EOFException}. - * - *

If {@code quiet=true} then encountering the end of the input (even after reading some data) - * will return {@code false}. - * - *

This is slightly different to the behaviour of {@link ExtractorInput#peekFully(byte[], int, - * int, boolean)}, where {@code allowEndOfInput=true} only returns false (and suppresses the - * exception) if the end of the input is reached before reading any data. - */ - private static boolean peekSafely( - ExtractorInput input, byte[] output, int offset, int length, boolean quiet) - throws IOException { - try { - return input.peekFully(output, offset, length, /* allowEndOfInput= */ quiet); - } catch (EOFException e) { - if (quiet) { - return false; - } else { - throw e; - } - } - } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java index 9604b48bee..8959caa323 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java @@ -16,12 +16,14 @@ package com.google.android.exoplayer2.extractor; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import java.io.EOFException; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,12 +36,12 @@ public class ExtractorUtilTest { private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8}; @Test - public void peekToLengthEndNotReached() throws Exception { + public void peekToLength_endNotReached() throws Exception { FakeDataSource testDataSource = new FakeDataSource(); testDataSource .getDataSet() .newDefaultData() - .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)) + .appendReadData(Arrays.copyOf(TEST_DATA, 3)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); @@ -57,12 +59,12 @@ public class ExtractorUtilTest { } @Test - public void peekToLengthEndReached() throws Exception { + public void peekToLength_endReached() throws Exception { FakeDataSource testDataSource = new FakeDataSource(); testDataSource .getDataSet() .newDefaultData() - .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)) + .appendReadData(Arrays.copyOf(TEST_DATA, 3)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); @@ -77,4 +79,135 @@ public class ExtractorUtilTest { assertThat(input.getPeekPosition()).isEqualTo(TEST_DATA.length); assertThat(target).isEqualTo(TEST_DATA); } + + @Test + public void readFullyQuietly_endNotReached_isTrueAndReadsData() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource + .getDataSet() + .newDefaultData() + .appendReadData(Arrays.copyOf(TEST_DATA, 3)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + byte[] target = new byte[TEST_DATA.length]; + int offset = 2; + int length = 4; + + boolean hasRead = ExtractorUtil.readFullyQuietly(input, target, offset, length); + + assertThat(hasRead).isTrue(); + assertThat(input.getPosition()).isEqualTo(length); + assertThat(Arrays.copyOfRange(target, offset, offset + length)) + .isEqualTo(Arrays.copyOf(TEST_DATA, length)); + } + + @Test + public void readFullyQuietly_endReached_isFalse() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData().appendReadData(Arrays.copyOf(TEST_DATA, 3)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + byte[] target = new byte[TEST_DATA.length]; + int offset = 0; + int length = TEST_DATA.length + 1; + + boolean hasRead = ExtractorUtil.readFullyQuietly(input, target, offset, length); + + assertThat(hasRead).isFalse(); + assertThat(input.getPosition()).isEqualTo(0); + } + + @Test + public void skipFullyQuietly_endNotReached_isTrueAndSkipsData() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource + .getDataSet() + .newDefaultData() + .appendReadData(Arrays.copyOf(TEST_DATA, 3)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + int length = 4; + + boolean hasRead = ExtractorUtil.skipFullyQuietly(input, length); + + assertThat(hasRead).isTrue(); + assertThat(input.getPosition()).isEqualTo(length); + } + + @Test + public void skipFullyQuietly_endReached_isFalse() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData().appendReadData(Arrays.copyOf(TEST_DATA, 3)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + int length = TEST_DATA.length + 1; + + boolean hasRead = ExtractorUtil.skipFullyQuietly(input, length); + + assertThat(hasRead).isFalse(); + assertThat(input.getPosition()).isEqualTo(0); + } + + @Test + public void peekFullyQuietly_endNotReached_isTrueAndPeeksData() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource + .getDataSet() + .newDefaultData() + .appendReadData(Arrays.copyOf(TEST_DATA, 3)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + byte[] target = new byte[TEST_DATA.length]; + int offset = 2; + int length = 4; + + boolean hasRead = + ExtractorUtil.peekFullyQuietly(input, target, offset, length, /* allowEndOfInput= */ false); + + assertThat(hasRead).isTrue(); + assertThat(input.getPeekPosition()).isEqualTo(length); + assertThat(Arrays.copyOfRange(target, offset, offset + length)) + .isEqualTo(Arrays.copyOf(TEST_DATA, length)); + } + + @Test + public void peekFullyQuietly_endReachedWithEndOfInputAllowed_isFalse() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData().appendReadData(Arrays.copyOf(TEST_DATA, 3)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + byte[] target = new byte[TEST_DATA.length]; + int offset = 0; + int length = TEST_DATA.length + 1; + + boolean hasRead = + ExtractorUtil.peekFullyQuietly(input, target, offset, length, /* allowEndOfInput= */ true); + + assertThat(hasRead).isFalse(); + assertThat(input.getPeekPosition()).isEqualTo(0); + } + + @Test + public void peekFullyQuietly_endReachedWithoutEndOfInputAllowed_throws() throws Exception { + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData().appendReadData(Arrays.copyOf(TEST_DATA, 3)); + testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); + ExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); + byte[] target = new byte[TEST_DATA.length]; + int offset = 0; + int length = TEST_DATA.length + 1; + + assertThrows( + EOFException.class, + () -> + ExtractorUtil.peekFullyQuietly( + input, target, offset, length, /* allowEndOfInput= */ false)); + assertThat(input.getPeekPosition()).isEqualTo(0); + } } From d699fb5dd9ae7aff8e52ad96e87808928f14f8aa Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 29 Jun 2021 20:19:50 +0100 Subject: [PATCH 27/48] Add info about trick-playness to the Format log string PiperOrigin-RevId: 382139109 --- .../src/main/java/com/google/android/exoplayer2/Format.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index 1f3e5bf824..d4eb5e13d2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -1630,6 +1630,9 @@ public final class Format implements Parcelable { if (format.label != null) { builder.append(", label=").append(format.label); } + if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) { + builder.append(", trick-play-track"); + } return builder.toString(); } From f5d8efbf407ed304a2de21f482f86a8f753ddcd7 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Jul 2021 17:31:50 +0100 Subject: [PATCH 28/48] Add `@Deprecated` to SEP methods that override deprecated methods Without this annotation it seems that `SimpleExoPlayer` effectively 'un-deprecates' the method, specifically: * A usage of these methods isn't flagged by Android Studio if the declared type is `SimpleExoPlayer` (up-casting to e.g. `ExoPlayer.VideoComponent` results in the warning showing up). * The `SimpleExoPlayer` javadoc doesn't mention this method is deprecated: https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html#addVideoListener(com.google.android.exoplayer2.video.VideoListener) * The Metalava API output for `SimpleExoPlayer` doesn't show these methods as deprecated. PiperOrigin-RevId: 382756174 --- RELEASENOTES.md | 4 ++++ .../google/android/exoplayer2/SimpleExoPlayer.java | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c8773b0785..12583fb132 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,10 @@ ### dev-v2 (not yet released) +* Core Library: + * Explicitly mark several methods on `SimpleExoPlayer` as `@Deprecated`. + These methods are all overrides and are already deprecated on `Player` + and the respective `ExoPlayer` component classes (since 2.14.0). * Video: * Fix `IncorrectContextUseViolation` strict mode warning on Android 11 ([#8246](https://github.com/google/ExoPlayer/pull/8246)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 6f8df8b996..bac2e82c60 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -963,6 +963,7 @@ public class SimpleExoPlayer extends BasePlayer player.removeAudioOffloadListener(listener); } + @Deprecated @Override public void addAudioListener(AudioListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -970,6 +971,7 @@ public class SimpleExoPlayer extends BasePlayer audioListeners.add(listener); } + @Deprecated @Override public void removeAudioListener(AudioListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1175,6 +1177,7 @@ public class SimpleExoPlayer extends BasePlayer return audioDecoderCounters; } + @Deprecated @Override public void addVideoListener(VideoListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1182,6 +1185,7 @@ public class SimpleExoPlayer extends BasePlayer videoListeners.add(listener); } + @Deprecated @Override public void removeVideoListener(VideoListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1236,6 +1240,7 @@ public class SimpleExoPlayer extends BasePlayer .send(); } + @Deprecated @Override public void addTextOutput(TextOutput listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1243,6 +1248,7 @@ public class SimpleExoPlayer extends BasePlayer textOutputs.add(listener); } + @Deprecated @Override public void removeTextOutput(TextOutput listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1255,6 +1261,7 @@ public class SimpleExoPlayer extends BasePlayer return currentCues; } + @Deprecated @Override public void addMetadataOutput(MetadataOutput listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1262,6 +1269,7 @@ public class SimpleExoPlayer extends BasePlayer metadataOutputs.add(listener); } + @Deprecated @Override public void removeMetadataOutput(MetadataOutput listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1297,6 +1305,7 @@ public class SimpleExoPlayer extends BasePlayer addListener(eventListener); } + @Deprecated @Override public void addListener(Player.EventListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1316,6 +1325,7 @@ public class SimpleExoPlayer extends BasePlayer removeListener(eventListener); } + @Deprecated @Override public void removeListener(Player.EventListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1582,6 +1592,7 @@ public class SimpleExoPlayer extends BasePlayer player.setForegroundMode(foregroundMode); } + @Deprecated @Override public void stop(boolean reset) { verifyApplicationThread(); @@ -1790,6 +1801,7 @@ public class SimpleExoPlayer extends BasePlayer } } + @Deprecated @Override public void addDeviceListener(DeviceListener listener) { // Don't verify application thread. We allow calls to this method from any thread. @@ -1797,6 +1809,7 @@ public class SimpleExoPlayer extends BasePlayer deviceListeners.add(listener); } + @Deprecated @Override public void removeDeviceListener(DeviceListener listener) { // Don't verify application thread. We allow calls to this method from any thread. From 3430912581b60a651082c51e6e2b9549e3cdefc2 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Jul 2021 18:07:15 +0100 Subject: [PATCH 29/48] Encode emsg duration & ID as int64 instead of uint32 The serialization scheme used here is custom, it doesn't need to be compatible with emsg-v0 or emsg-v1 (since https://github.com/google/ExoPlayer/commit/97183ef55866170807910cd626264d82d41d46d4). This means that C.TIME_UNSET will propagate correctly through the serialization. Issue: #9123 PiperOrigin-RevId: 382762873 --- RELEASENOTES.md | 3 +++ .../metadata/emsg/EventMessageDecoder.java | 4 ++-- .../metadata/emsg/EventMessageEncoder.java | 13 ++--------- .../emsg/EventMessageDecoderTest.java | 4 ++-- .../emsg/EventMessageEncoderTest.java | 22 +++++++++++++++---- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 12583fb132..5f9846a061 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -37,6 +37,9 @@ ([#8960](https://github.com/google/ExoPlayer/issues/8960)). * DRM: * Allow repeated provisioning in `DefaultDrmSession(Manager)`. +* Metadata: + * Fix handling of emsg messages with an unset duration + ([#9123](https://github.com/google/ExoPlayer/issues/9123)). * UI: * Add `PendingIntent.FLAG_IMMUTABLE` flag when creating a broadcast intent in `PlayerNotificationManager`. This is required to avoid an error on diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java index 8a7e1851c6..999f0228bd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -35,8 +35,8 @@ public final class EventMessageDecoder extends SimpleMetadataDecoder { public EventMessage decode(ParsableByteArray emsgData) { String schemeIdUri = Assertions.checkNotNull(emsgData.readNullTerminatedString()); String value = Assertions.checkNotNull(emsgData.readNullTerminatedString()); - long durationMs = emsgData.readUnsignedInt(); - long id = emsgData.readUnsignedInt(); + long durationMs = emsgData.readLong(); + long id = emsgData.readLong(); byte[] messageData = Arrays.copyOfRange(emsgData.getData(), emsgData.getPosition(), emsgData.limit()); return new EventMessage(schemeIdUri, value, durationMs, id, messageData); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java index 4fa3f71b32..81c11ba48c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java @@ -45,8 +45,8 @@ public final class EventMessageEncoder { writeNullTerminatedString(dataOutputStream, eventMessage.schemeIdUri); String nonNullValue = eventMessage.value != null ? eventMessage.value : ""; writeNullTerminatedString(dataOutputStream, nonNullValue); - writeUnsignedInt(dataOutputStream, eventMessage.durationMs); - writeUnsignedInt(dataOutputStream, eventMessage.id); + dataOutputStream.writeLong(eventMessage.durationMs); + dataOutputStream.writeLong(eventMessage.id); dataOutputStream.write(eventMessage.messageData); dataOutputStream.flush(); return byteArrayOutputStream.toByteArray(); @@ -61,13 +61,4 @@ public final class EventMessageEncoder { dataOutputStream.writeBytes(value); dataOutputStream.writeByte(0); } - - private static void writeUnsignedInt(DataOutputStream outputStream, long value) - throws IOException { - outputStream.writeByte((int) (value >>> 24) & 0xFF); - outputStream.writeByte((int) (value >>> 16) & 0xFF); - outputStream.writeByte((int) (value >>> 8) & 0xFF); - outputStream.writeByte((int) value & 0xFF); - } - } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java index ed06eb0aff..2bd02446da 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java @@ -37,8 +37,8 @@ public final class EventMessageDecoderTest { Bytes.concat( createByteArray(117, 114, 110, 58, 116, 101, 115, 116, 0), // scheme_id_uri = "urn:test" createByteArray(49, 50, 51, 0), // value = "123" - createByteArray(0, 0, 11, 184), // event_duration_ms = 3000 - createByteArray(0, 15, 67, 211), // id = 1000403 + createByteArray(0, 0, 0, 0, 0, 0, 11, 184), // event_duration_ms = 3000 + createByteArray(0, 0, 0, 0, 0, 15, 67, 211), // id = 1000403 createByteArray(0, 1, 2, 3, 4)); // message_data = {0, 1, 2, 3, 4} EventMessageDecoder decoder = new EventMessageDecoder(); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java b/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java index c6d2231eb2..4a484943b8 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java @@ -18,9 +18,11 @@ package com.google.android.exoplayer2.metadata.emsg; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.common.primitives.Bytes; import java.io.IOException; import java.nio.ByteBuffer; @@ -38,8 +40,8 @@ public final class EventMessageEncoderTest { Bytes.concat( createByteArray(117, 114, 110, 58, 116, 101, 115, 116, 0), // scheme_id_uri = "urn:test" createByteArray(49, 50, 51, 0), // value = "123" - createByteArray(0, 0, 11, 184), // event_duration_ms = 3000 - createByteArray(0, 15, 67, 211), // id = 1000403 + createByteArray(0, 0, 0, 0, 0, 0, 11, 184), // event_duration_ms = 3000 + createByteArray(0, 0, 0, 0, 0, 15, 67, 211), // id = 1000403 createByteArray(0, 1, 2, 3, 4)); // message_data = {0, 1, 2, 3, 4} @Test @@ -67,8 +69,8 @@ public final class EventMessageEncoderTest { Bytes.concat( createByteArray(117, 114, 110, 58, 116, 101, 115, 116, 0), // scheme_id_uri = "urn:test" createByteArray(49, 50, 51, 0), // value = "123" - createByteArray(0, 0, 11, 184), // event_duration_ms = 3000 - createByteArray(0, 15, 67, 210), // id = 1000402 + createByteArray(0, 0, 0, 0, 0, 0, 11, 184), // event_duration_ms = 3000 + createByteArray(0, 0, 0, 0, 0, 15, 67, 210), // id = 1000402 createByteArray(4, 3, 2, 1, 0)); // message_data = {4, 3, 2, 1, 0} EventMessageEncoder eventMessageEncoder = new EventMessageEncoder(); @@ -78,6 +80,18 @@ public final class EventMessageEncoderTest { assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1); } + // https://github.com/google/ExoPlayer/issues/9123 + @Test + public void encodeDecodeEventMessage_durationNotSet() { + EventMessage originalMessage = + new EventMessage("urn:test", "456", C.TIME_UNSET, 99, new byte[] {7, 8, 9}); + byte[] encodedMessage = new EventMessageEncoder().encode(originalMessage); + EventMessage decodedMessage = + new EventMessageDecoder().decode(new ParsableByteArray(encodedMessage)); + + assertThat(decodedMessage).isEqualTo(originalMessage); + } + /** Converts an array of integers in the range [0, 255] into an equivalent byte array. */ // TODO(internal b/161804035): Move to a single file. private static byte[] createByteArray(int... bytes) { From 306b2e6d2e41163ef79322ed329411f98858995b Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Jul 2021 18:09:18 +0100 Subject: [PATCH 30/48] Don't propagate attrs into child SubtitleOutput from SubtitleView PiperOrigin-RevId: 382763308 --- RELEASENOTES.md | 3 +++ .../java/com/google/android/exoplayer2/ui/SubtitleView.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5f9846a061..75011909e0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -52,6 +52,9 @@ * Fix `StyledPlayerView` and `StyledPlayerControlView` popup menu items not expanding to occupy the full width of the popup ([#9086](https://github.com/google/ExoPlayer/issues/9086)). + * Don't propagate `AttributeSet` from `SubtitleView` constructor into + `CanvasSubtitleOutput`. Just passing the `Context` is enough, and + ensures programmatic changes to the `SubtitleView` will propagate down. * RTSP * Fix session description (SDP) parsing to use a HashMap-like behaviour for duplicated attributes. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index ace00fb983..5926b2dbde 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -140,7 +140,7 @@ public final class SubtitleView extends FrameLayout implements TextOutput { applyEmbeddedStyles = true; applyEmbeddedFontSizes = true; - CanvasSubtitleOutput canvasSubtitleOutput = new CanvasSubtitleOutput(context, attrs); + CanvasSubtitleOutput canvasSubtitleOutput = new CanvasSubtitleOutput(context); output = canvasSubtitleOutput; innerSubtitleView = canvasSubtitleOutput; addView(innerSubtitleView); From 278593f0c831540a6590860349b604f5a6a0617f Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Jul 2021 18:10:46 +0100 Subject: [PATCH 31/48] Use the content URI as well as mediaId for the auto-generated ad ID MediaItem.mediaId used to default to the content URI, but this changed: https://github.com/google/ExoPlayer/commit/cc26a92e070db8963738beed423bb4699762877f Before the mediaId change linked above, a playlist of different content all with the same ad URI would play the ads for every item. After the change the ad would only play once (because mediaId == "" for every item, so they're all the same). This change restores roughly the original behaviour by always considering both mediaId and the content URI. Issue: #9106 PiperOrigin-RevId: 382763618 --- RELEASENOTES.md | 4 ++++ docs/ad-insertion.md | 12 ++++++------ .../exoplayer2/source/DefaultMediaSourceFactory.java | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 75011909e0..9011ac21bd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -35,6 +35,10 @@ ([#9004](https://github.com/google/ExoPlayer/issues/9004)). * Forward the FRAME-RATE value from the master playlist to renditions. ([#8960](https://github.com/google/ExoPlayer/issues/8960)). +* Ad playback: + * Use the content URI when auto-generating an ad ID (in addition to the + media ID and ad tag URI) + ([#9106](https://github.com/google/ExoPlayer/issues/9106). * DRM: * Allow repeated provisioning in `DefaultDrmSession(Manager)`. * Metadata: diff --git a/docs/ad-insertion.md b/docs/ad-insertion.md index 8ca6e00ba7..972f07a9c5 100644 --- a/docs/ad-insertion.md +++ b/docs/ad-insertion.md @@ -67,12 +67,12 @@ described below. ### Playlists with ads ### When playing a [playlist][] with multiple media items, the default behavior is -to request the ad tag and store ad playback state once for each media ID and ad -tag URI combination. This means that users will see ads for every media item -with ads that has a distinct media ID, even if the ad tag URIs match. If a -media item is repeated, the user will see the corresponding ads only once (the -ad playback state stores whether ads have been played, so they are skipped -after their first occurrence). +to request the ad tag and store ad playback state once for each media ID, +content URI and ad tag URI combination. This means that users will see ads for +every media item with ads that has a distinct media ID or content URI, even if +the ad tag URIs match. If a media item is repeated, the user will see the +corresponding ads only once (the ad playback state stores whether ads have been +played, so they are skipped after their first occurrence). It's possible to customize this behavior by passing an opaque ads identifier with which ad playback state for a given media item is linked, based on object diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java index a7bd656294..0f5554401a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.content.Context; -import android.util.Pair; import android.util.SparseArray; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -40,6 +39,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import java.util.Arrays; import java.util.List; @@ -427,7 +427,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { new DataSpec(adsConfiguration.adTagUri), /* adsId= */ adsConfiguration.adsId != null ? adsConfiguration.adsId - : Pair.create(mediaItem.mediaId, adsConfiguration.adTagUri), + : ImmutableList.of( + mediaItem.mediaId, mediaItem.playbackProperties.uri, adsConfiguration.adTagUri), /* adMediaSourceFactory= */ this, adsLoader, adViewProvider); From 17c6092335f8abe264298f4edc2ae13f141bec3d Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 2 Jul 2021 18:17:37 +0100 Subject: [PATCH 32/48] Clarify the thread requirements of a SurfaceView or SurfaceHolder Issue: #9005 PiperOrigin-RevId: 382765045 --- .../main/java/com/google/android/exoplayer2/Player.java | 9 +++++++++ .../java/com/google/android/exoplayer2/ExoPlayer.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 50739cfc24..558c930773 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -1761,6 +1761,9 @@ public interface Player { * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be * rendered. The player will track the lifecycle of the surface automatically. * + *

The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread + * associated with {@link #getApplicationLooper()}. + * * @param surfaceHolder The surface holder. */ void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); @@ -1777,6 +1780,9 @@ public interface Player { * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the * lifecycle of the surface automatically. * + *

The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread + * associated with {@link #getApplicationLooper()}. + * * @param surfaceView The surface view. */ void setVideoSurfaceView(@Nullable SurfaceView surfaceView); @@ -1793,6 +1799,9 @@ public interface Player { * Sets the {@link TextureView} onto which video will be rendered. The player will track the * lifecycle of the surface automatically. * + *

The thread that calls the {@link TextureView.SurfaceTextureListener} methods must be the + * thread associated with {@link #getApplicationLooper()}. + * * @param textureView The texture view. */ void setVideoTextureView(@Nullable TextureView textureView); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index de1e8a00f4..c98e78e159 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -334,6 +334,9 @@ public interface ExoPlayer extends Player { * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be * rendered. The player will track the lifecycle of the surface automatically. * + *

The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread + * associated with {@link #getApplicationLooper()}. + * * @param surfaceHolder The surface holder. */ void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder); @@ -350,6 +353,9 @@ public interface ExoPlayer extends Player { * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the * lifecycle of the surface automatically. * + *

The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread + * associated with {@link #getApplicationLooper()}. + * * @param surfaceView The surface view. */ void setVideoSurfaceView(@Nullable SurfaceView surfaceView); @@ -366,6 +372,9 @@ public interface ExoPlayer extends Player { * Sets the {@link TextureView} onto which video will be rendered. The player will track the * lifecycle of the surface automatically. * + *

The thread that calls the {@link TextureView.SurfaceTextureListener} methods must be the + * thread associated with {@link #getApplicationLooper()}. + * * @param textureView The texture view. */ void setVideoTextureView(@Nullable TextureView textureView); From d587420650f2117ce4c9221b905055758e148c98 Mon Sep 17 00:00:00 2001 From: kimvde Date: Thu, 8 Jul 2021 18:17:28 +0100 Subject: [PATCH 33/48] Add support for MP4 H263 atom type #minor-release Issue:#9158 PiperOrigin-RevId: 383660258 --- RELEASENOTES.md | 2 ++ .../java/com/google/android/exoplayer2/extractor/mp4/Atom.java | 2 ++ .../google/android/exoplayer2/extractor/mp4/AtomParsers.java | 3 +++ 3 files changed, 7 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9011ac21bd..012468888d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -25,6 +25,8 @@ is malformed and should be re-encoded. * Improve support for truncated Ogg streams ([#7608](https://github.com/google/ExoPlayer/issues/7608)). + * Add support for MP4 H263 atom type + ([#9158](https://github.com/google/ExoPlayer/issues/9158)). * HLS: * Fix issue where playback of a live event could become stuck rather than transitioning to `STATE_ENDED` when the event ends diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index 26f2211135..273421a813 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -106,6 +106,8 @@ import java.util.List; @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_s263 = 0x73323633; + public static final int TYPE_H263 = 0x48323633; + @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_d263 = 0x64323633; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index fad93f5a15..dea3113b61 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -915,6 +915,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 || childAtomType == Atom.TYPE_s263 + || childAtomType == Atom.TYPE_H263 || childAtomType == Atom.TYPE_vp08 || childAtomType == Atom.TYPE_vp09 || childAtomType == Atom.TYPE_av01 @@ -1056,6 +1057,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Nullable String mimeType = null; if (atomType == Atom.TYPE_m1v_) { mimeType = MimeTypes.VIDEO_MPEG; + } else if (atomType == Atom.TYPE_H263) { + mimeType = MimeTypes.VIDEO_H263; } @Nullable List initializationData = null; From c7d34d768d3f3f45fbfec2096c49144db5e2b3d1 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 12 Jul 2021 15:14:47 +0100 Subject: [PATCH 34/48] The sound, vibrate and ticker will only be played once Android doc:https://developer.android.com/reference/android/app/Notification.Builder#setOnlyAlertOnce(boolean) PiperOrigin-RevId: 384227580 --- .../google/android/exoplayer2/ui/PlayerNotificationManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 6c3441a849..f59a211504 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -1389,6 +1389,7 @@ public class PlayerNotificationManager { builder.setGroup(groupKey); } + builder.setOnlyAlertOnce(true); return builder; } From a47eb8a08a0ba4a75afe901be05bb8792e5279ea Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 12 Jul 2021 17:39:47 +0100 Subject: [PATCH 35/48] Explicitly override all non-deprecated methods in `Player.Listener` Most of the super-interfaces are deprecated, but the intention is that only the types are deprecated and the methods themselves shouldn't be. In order to reflect this in javadoc we override all the methods in `Player.Listener` in order to 'cancel' the deprecation. This change deliberately doesn't override methods that are explicitly deprecated with documented replacements (like `Player.EventListener#onPlayerStateChanged`) - these should contine to be marked as deprecated in javadoc. PiperOrigin-RevId: 384253725 --- .../com/google/android/exoplayer2/Player.java | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 558c930773..0bb973d4ef 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -325,6 +325,9 @@ public interface Player { *

State changes and events that happen within one {@link Looper} message queue iteration are * reported together and only after all individual callbacks were triggered. * + *

Only state changes represented by {@link EventFlags events} are reported through this + * method. + * *

Listeners should prefer this method over individual callbacks in the following cases: * *

@@ -869,7 +869,7 @@ public final Returns:
Whether the player is playing.
See Also:
-
Player.EventListener.onIsPlayingChanged(boolean)
+
Player.Listener.onIsPlayingChanged(boolean)
@@ -1127,7 +1127,7 @@ public final Specified by:
getCurrentMediaItem in interface Player
See Also:
-
Player.EventListener.onMediaItemTransition(MediaItem, int)
+
Player.Listener.onMediaItemTransition(MediaItem, int)
diff --git a/docs/doc/reference/com/google/android/exoplayer2/C.html b/docs/doc/reference/com/google/android/exoplayer2/C.html index 8f46902509..040d584617 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/C.html +++ b/docs/doc/reference/com/google/android/exoplayer2/C.html @@ -4718,7 +4718,8 @@ public static final int MSG_CUSTOM_BASE
@@ -2851,7 +2851,7 @@ int getPlaybackState()
Returns:
The current playback state.
See Also:
-
Player.EventListener.onPlaybackStateChanged(int)
+
Player.Listener.onPlaybackStateChanged(int)
@@ -2869,7 +2869,7 @@ int getPlaybackSuppressionReason()
Returns:
The current playback suppression reason.
See Also:
-
Player.EventListener.onPlaybackSuppressionReasonChanged(int)
+
Player.Listener.onPlaybackSuppressionReasonChanged(int)
@@ -2893,7 +2893,7 @@ int getPlaybackSuppressionReason()
Returns:
Whether the player is playing.
See Also:
-
Player.EventListener.onIsPlayingChanged(boolean)
+
Player.Listener.onIsPlayingChanged(boolean)
@@ -2906,7 +2906,7 @@ int getPlaybackSuppressionReason()
@Nullable
 ExoPlaybackException getPlayerError()
Returns the error that caused playback to fail. This is the same error that will have been - reported via Player.EventListener.onPlayerError(ExoPlaybackException) at the time of failure. It + reported via Player.Listener.onPlayerError(ExoPlaybackException) at the time of failure. It can be queried using this method until the player is re-prepared.

Note that this method will always return null if getPlaybackState() is not @@ -2915,7 +2915,7 @@ int getPlaybackSuppressionReason()

Returns:
The error, or null.
See Also:
-
Player.EventListener.onPlayerError(ExoPlaybackException)
+
Player.Listener.onPlayerError(ExoPlaybackException)
@@ -2982,7 +2982,7 @@ int getPlaybackSuppressionReason()
Returns:
Whether playback will proceed when ready.
See Also:
-
Player.EventListener.onPlayWhenReadyChanged(boolean, int)
+
Player.Listener.onPlayWhenReadyChanged(boolean, int)
@@ -3014,7 +3014,7 @@ int getRepeatMode()
Returns:
The current repeat mode.
See Also:
-
Player.EventListener.onRepeatModeChanged(int)
+
Player.Listener.onRepeatModeChanged(int)
@@ -3042,7 +3042,7 @@ int getRepeatMode()
Returns whether shuffling of windows is enabled.
See Also:
-
Player.EventListener.onShuffleModeEnabledChanged(boolean)
+
Player.Listener.onShuffleModeEnabledChanged(boolean)
@@ -3058,7 +3058,7 @@ int getRepeatMode()
Returns:
Whether the player is currently loading the source.
See Also:
-
Player.EventListener.onIsLoadingChanged(boolean)
+
Player.Listener.onIsLoadingChanged(boolean)
@@ -3200,7 +3200,7 @@ int getRepeatMode()
Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the player to the default, which means there is no speed or pitch adjustment. -

Playback parameters changes may cause the player to buffer. Player.EventListener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently +

Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently active playback parameters change.

Parameters:
@@ -3236,7 +3236,7 @@ int getRepeatMode()
Returns the currently active playback parameters.
See Also:
-
Player.EventListener.onPlaybackParametersChanged(PlaybackParameters)
+
Player.Listener.onPlaybackParametersChanged(PlaybackParameters)
@@ -3294,7 +3294,7 @@ void stop​(boolean reset)
Returns the available track groups.
See Also:
-
Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
+
Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
@@ -3312,7 +3312,7 @@ void stop​(boolean reset) components that is not assigned any selected tracks.
See Also:
-
Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
+
Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
@@ -3334,7 +3334,7 @@ void stop​(boolean reset) rather than being timed (or dynamic) metadata, which is represented within a metadata track.
See Also:
-
Player.EventListener.onStaticMetadataChanged(List)
+
Player.Listener.onStaticMetadataChanged(List)
@@ -3349,7 +3349,7 @@ void stop​(boolean reset) supported.

This MediaMetadata is a combination of the MediaItem.mediaMetadata and the - static and dynamic metadata sourced from Player.EventListener.onStaticMetadataChanged(List) and + static and dynamic metadata sourced from Player.Listener.onStaticMetadataChanged(List) and MetadataOutput.onMetadata(Metadata). @@ -3374,7 +3374,7 @@ void stop​(boolean reset)

Returns the current Timeline. Never null, but may be empty.
See Also:
-
Player.EventListener.onTimelineChanged(Timeline, int)
+
Player.Listener.onTimelineChanged(Timeline, int)
@@ -3455,7 +3455,7 @@ void stop​(boolean reset) empty.
See Also:
-
Player.EventListener.onMediaItemTransition(MediaItem, int)
+
Player.Listener.onMediaItemTransition(MediaItem, int)
@@ -3759,7 +3759,10 @@ void stop​(boolean reset)
void setVideoSurfaceHolder​(@Nullable
                            SurfaceHolder surfaceHolder)
Sets the SurfaceHolder that holds the Surface onto which video will be - rendered. The player will track the lifecycle of the surface automatically.
+ rendered. The player will track the lifecycle of the surface automatically. + +

The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with getApplicationLooper().

Parameters:
surfaceHolder - The surface holder.
@@ -3791,7 +3794,10 @@ void stop​(boolean reset)
void setVideoSurfaceView​(@Nullable
                          SurfaceView surfaceView)
Sets the SurfaceView onto which video will be rendered. The player will track the - lifecycle of the surface automatically.
+ lifecycle of the surface automatically. + +

The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with getApplicationLooper().

Parameters:
surfaceView - The surface view.
@@ -3823,7 +3829,10 @@ void stop​(boolean reset)
void setVideoTextureView​(@Nullable
                          TextureView textureView)
Sets the TextureView onto which video will be rendered. The player will track the - lifecycle of the surface automatically.
+ lifecycle of the surface automatically. + +

The thread that calls the TextureView.SurfaceTextureListener methods must be the + thread associated with getApplicationLooper().

Parameters:
textureView - The texture view.
@@ -3859,7 +3868,7 @@ void stop​(boolean reset) determined yet.
See Also:
-
VideoListener.onVideoSizeChanged(VideoSize)
+
Player.Listener.onVideoSizeChanged(VideoSize)
diff --git a/docs/doc/reference/com/google/android/exoplayer2/PlayerMessage.html b/docs/doc/reference/com/google/android/exoplayer2/PlayerMessage.html index 95bf9ea93f..95fc1e2542 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/PlayerMessage.html +++ b/docs/doc/reference/com/google/android/exoplayer2/PlayerMessage.html @@ -648,7 +648,7 @@ public public PlayerMessage send()
Sends the message. If the target throws an ExoPlaybackException then it is propagated - out of the player as an error using Player.EventListener.onPlayerError(ExoPlaybackException).
+ out of the player as an error using Player.Listener.onPlayerError(ExoPlaybackException).
Returns:
This message.
diff --git a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html index 8c068713e5..5b04a29d11 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html +++ b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.Builder.html @@ -542,7 +542,7 @@ extends experimentalSetForegroundModeTimeoutMs
public SimpleExoPlayer.Builder experimentalSetForegroundModeTimeoutMs​(long timeoutMs)
Set a limit on the time a call to SimpleExoPlayer.setForegroundMode(boolean) can spend. If a call to SimpleExoPlayer.setForegroundMode(boolean) takes more than timeoutMs milliseconds to complete, the player - will raise an error via Player.EventListener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException). + will raise an error via Player.Listener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException).

This method is experimental, and will be renamed or removed in a future release.

@@ -841,7 +841,7 @@ extends Sets a timeout for calls to SimpleExoPlayer.release() and SimpleExoPlayer.setForegroundMode(boolean).

If a call to SimpleExoPlayer.release() or SimpleExoPlayer.setForegroundMode(boolean) takes more than - timeoutMs to complete, the player will report an error via Player.EventListener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException). + timeoutMs to complete, the player will report an error via Player.Listener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException).

Parameters:
releaseTimeoutMs - The release timeout, in milliseconds.
@@ -862,7 +862,7 @@ extends Sets a timeout for detaching a surface from the player.

If detaching a surface or replacing a surface takes more than - detachSurfaceTimeoutMs to complete, the player will report an error via Player.EventListener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException). + detachSurfaceTimeoutMs to complete, the player will report an error via Player.Listener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException).

Parameters:
detachSurfaceTimeoutMs - The timeout for detaching a surface, in milliseconds.
@@ -882,7 +882,7 @@ extends public SimpleExoPlayer.Builder setPauseAtEndOfMediaItems​(boolean pauseAtEndOfMediaItems)
Sets whether to pause playback at the end of each media item. -

This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.EventListener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

+

This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

Parameters:
pauseAtEndOfMediaItems - Whether to pause playback at the end of each media item.
diff --git a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html index e6ebba781a..27da82f2bf 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":10,"i71":10,"i72":10,"i73":10,"i74":10,"i75":10,"i76":10,"i77":10,"i78":10,"i79":10,"i80":10,"i81":10,"i82":42,"i83":42,"i84":10,"i85":10,"i86":10,"i87":10,"i88":10,"i89":10,"i90":10,"i91":10,"i92":10,"i93":10,"i94":10,"i95":42,"i96":10,"i97":10,"i98":10,"i99":10,"i100":10,"i101":10,"i102":10,"i103":10,"i104":10,"i105":42,"i106":10,"i107":10,"i108":10,"i109":10,"i110":10,"i111":10,"i112":10,"i113":10,"i114":10,"i115":10,"i116":10,"i117":10,"i118":10,"i119":10,"i120":10,"i121":10,"i122":10,"i123":42,"i124":10,"i125":10,"i126":10,"i127":10,"i128":10,"i129":10,"i130":10,"i131":10,"i132":10}; +var data = {"i0":10,"i1":42,"i2":10,"i3":42,"i4":42,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":42,"i12":42,"i13":42,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10,"i23":10,"i24":10,"i25":10,"i26":10,"i27":10,"i28":10,"i29":10,"i30":10,"i31":10,"i32":10,"i33":10,"i34":10,"i35":10,"i36":10,"i37":10,"i38":10,"i39":10,"i40":10,"i41":10,"i42":10,"i43":10,"i44":10,"i45":10,"i46":10,"i47":10,"i48":10,"i49":10,"i50":10,"i51":10,"i52":10,"i53":10,"i54":10,"i55":10,"i56":10,"i57":10,"i58":10,"i59":10,"i60":10,"i61":10,"i62":10,"i63":10,"i64":10,"i65":10,"i66":10,"i67":10,"i68":10,"i69":10,"i70":10,"i71":10,"i72":10,"i73":10,"i74":10,"i75":10,"i76":10,"i77":10,"i78":10,"i79":10,"i80":10,"i81":10,"i82":42,"i83":42,"i84":10,"i85":10,"i86":42,"i87":10,"i88":42,"i89":42,"i90":10,"i91":10,"i92":42,"i93":42,"i94":42,"i95":42,"i96":10,"i97":10,"i98":10,"i99":10,"i100":10,"i101":10,"i102":10,"i103":10,"i104":10,"i105":42,"i106":10,"i107":10,"i108":10,"i109":10,"i110":10,"i111":10,"i112":10,"i113":10,"i114":10,"i115":10,"i116":10,"i117":10,"i118":10,"i119":10,"i120":10,"i121":10,"i122":10,"i123":42,"i124":10,"i125":10,"i126":10,"i127":10,"i128":10,"i129":10,"i130":10,"i131":10,"i132":42}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"],32:["t6","Deprecated Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -305,7 +305,7 @@ implements void addAudioListener​(AudioListener listener) -
Adds a listener to receive audio events.
+
Deprecated.
@@ -319,14 +319,14 @@ implements void addDeviceListener​(DeviceListener listener) -
Adds a listener to receive device events.
+
Deprecated.
void addListener​(Player.EventListener listener) -
Registers a listener to receive events from the player.
+
Deprecated.
@@ -378,21 +378,21 @@ implements void addMetadataOutput​(MetadataOutput listener) -
Adds a MetadataOutput to receive metadata.
+
Deprecated.
void addTextOutput​(TextOutput listener) -
Registers an output to receive text events.
+
Deprecated.
void addVideoListener​(VideoListener listener) -
Adds a listener to receive video events.
+
Deprecated.
@@ -923,7 +923,7 @@ implements void removeAudioListener​(AudioListener listener) -
Removes a listener of audio events.
+
Deprecated.
@@ -937,14 +937,14 @@ implements void removeDeviceListener​(DeviceListener listener) -
Removes a listener of device events.
+
Deprecated.
void removeListener​(Player.EventListener listener) -
Unregister a listener registered through Player.addListener(EventListener).
+
Deprecated.
@@ -966,21 +966,21 @@ implements void removeMetadataOutput​(MetadataOutput listener) -
Removes a MetadataOutput.
+
Deprecated.
void removeTextOutput​(TextOutput listener) -
Removes a text output.
+
Deprecated.
void removeVideoListener​(VideoListener listener) -
Removes a listener of video events.
+
Deprecated.
@@ -1266,7 +1266,9 @@ implements void stop​(boolean reset) -  + +
Deprecated.
+
@@ -1661,7 +1663,10 @@ public int getVideoScalingMode() SurfaceHolder surfaceHolder)
Description copied from interface: Player
Sets the SurfaceHolder that holds the Surface onto which video will be - rendered. The player will track the lifecycle of the surface automatically.
+ rendered. The player will track the lifecycle of the surface automatically. + +

The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with Player.getApplicationLooper().

Specified by:
setVideoSurfaceHolder in interface ExoPlayer.VideoComponent
@@ -1703,7 +1708,10 @@ public int getVideoScalingMode() SurfaceView surfaceView)
Description copied from interface: Player
Sets the SurfaceView onto which video will be rendered. The player will track the - lifecycle of the surface automatically.
+ lifecycle of the surface automatically. + +

The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with Player.getApplicationLooper().

Specified by:
setVideoSurfaceView in interface ExoPlayer.VideoComponent
@@ -1745,7 +1753,10 @@ public int getVideoScalingMode() TextureView textureView)
Description copied from interface: Player
Sets the TextureView onto which video will be rendered. The player will track the - lifecycle of the surface automatically.
+ lifecycle of the surface automatically. + +

The thread that calls the TextureView.SurfaceTextureListener methods must be the + thread associated with Player.getApplicationLooper().

Specified by:
setVideoTextureView in interface ExoPlayer.VideoComponent
@@ -1817,7 +1828,9 @@ public int getVideoScalingMode() @@ -2509,7 +2540,7 @@ public int getPlaybackSuppressionReason() public ExoPlaybackException getPlayerError()
Description copied from interface: Player
Returns the error that caused playback to fail. This is the same error that will have been - reported via Player.EventListener.onPlayerError(ExoPlaybackException) at the time of failure. It + reported via Player.Listener.onPlayerError(ExoPlaybackException) at the time of failure. It can be queried using this method until the player is re-prepared.

Note that this method will always return null if Player.getPlaybackState() is not @@ -2520,7 +2551,7 @@ public Returns:

The error, or null.
See Also:
-
Player.EventListener.onPlayerError(ExoPlaybackException)
+
Player.Listener.onPlayerError(ExoPlaybackException)
@@ -2551,7 +2582,7 @@ public void retry()
Description copied from interface: Player
Returns the player's currently available Player.Commands. -

The returned Player.Commands are not updated when available commands change. Use Player.EventListener.onAvailableCommandsChanged(Commands) to get an update when the available commands +

The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands change.

Executing a command that is not available (for example, calling Player.next() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will neither throw an exception nor generate @@ -2565,7 +2596,7 @@ public void retry()

Returns:
The currently available Player.Commands.
See Also:
-
Player.EventListener.onAvailableCommandsChanged(com.google.android.exoplayer2.Player.Commands)
+
Player.Listener.onAvailableCommandsChanged(com.google.android.exoplayer2.Player.Commands)
@@ -2973,7 +3004,7 @@ public void prepare​(Returns:
Whether playback will proceed when ready.
See Also:
-
Player.EventListener.onPlayWhenReadyChanged(boolean, int)
+
Player.Listener.onPlayWhenReadyChanged(boolean, int)
@@ -2987,7 +3018,7 @@ public void prepare​(Description copied from interface: ExoPlayer
Sets whether to pause playback at the end of each media item. -

This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.EventListener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

+

This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

Specified by:
setPauseAtEndOfMediaItems in interface ExoPlayer
@@ -3029,7 +3060,7 @@ public int getRepeatMode()
Returns:
The current repeat mode.
See Also:
-
Player.EventListener.onRepeatModeChanged(int)
+
Player.Listener.onRepeatModeChanged(int)
@@ -3081,7 +3112,7 @@ public int getRepeatMode()
Specified by:
getShuffleModeEnabled in interface Player
See Also:
-
Player.EventListener.onShuffleModeEnabledChanged(boolean)
+
Player.Listener.onShuffleModeEnabledChanged(boolean)
@@ -3100,7 +3131,7 @@ public int getRepeatMode()
Returns:
Whether the player is currently loading the source.
See Also:
-
Player.EventListener.onIsLoadingChanged(boolean)
+
Player.Listener.onIsLoadingChanged(boolean)
@@ -3135,7 +3166,7 @@ public int getRepeatMode()
Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the player to the default, which means there is no speed or pitch adjustment. -

Playback parameters changes may cause the player to buffer. Player.EventListener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently +

Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently active playback parameters change.

Specified by:
@@ -3158,7 +3189,7 @@ public int getRepeatMode()
Specified by:
getPlaybackParameters in interface Player
See Also:
-
Player.EventListener.onPlaybackParametersChanged(PlaybackParameters)
+
Player.Listener.onPlaybackParametersChanged(PlaybackParameters)
@@ -3241,7 +3272,9 @@ public int getRepeatMode() @@ -3370,7 +3403,7 @@ public Specified by:
getCurrentTrackSelections in interface Player
See Also:
-
Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
+
Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
@@ -3395,7 +3428,7 @@ public Specified by:
getCurrentStaticMetadata in interface Player
See Also:
-
Player.EventListener.onStaticMetadataChanged(List)
+
Player.Listener.onStaticMetadataChanged(List)
@@ -3411,7 +3444,7 @@ public MediaMetadata is a combination of the MediaItem.mediaMetadata and the - static and dynamic metadata sourced from Player.EventListener.onStaticMetadataChanged(List) and + static and dynamic metadata sourced from Player.Listener.onStaticMetadataChanged(List) and MetadataOutput.onMetadata(Metadata).
Specified by:
@@ -3432,7 +3465,7 @@ public Specified by:
getCurrentTimeline in interface Player
See Also:
-
Player.EventListener.onTimelineChanged(Timeline, int)
+
Player.Listener.onTimelineChanged(Timeline, int)
@@ -3665,7 +3698,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)
  • addDeviceListener

    -
    public void addDeviceListener​(DeviceListener listener)
    +
    @Deprecated
    +public void addDeviceListener​(DeviceListener listener)
    +
    Deprecated.
    Description copied from interface: ExoPlayer.DeviceComponent
    Adds a listener to receive device events.
    @@ -3680,7 +3715,9 @@ public void setHandleWakeLock​(boolean handleWakeLock)
  • removeDeviceListener

    -
    public void removeDeviceListener​(DeviceListener listener)
    +
    @Deprecated
    +public void removeDeviceListener​(DeviceListener listener)
    +
    Deprecated.
    Description copied from interface: ExoPlayer.DeviceComponent
    Removes a listener of device events.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/StarRating.html b/docs/doc/reference/com/google/android/exoplayer2/StarRating.html index cf27f90aa3..9d6288d415 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/StarRating.html +++ b/docs/doc/reference/com/google/android/exoplayer2/StarRating.html @@ -328,7 +328,8 @@ extends Rat @@ -1240,7 +1240,7 @@ public int getPlaybackSuppressionReason()
    Returns:
    The current playback suppression reason.
    See Also:
    -
    Player.EventListener.onPlaybackSuppressionReasonChanged(int)
    +
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
  • @@ -1254,7 +1254,7 @@ public int getPlaybackSuppressionReason() public ExoPlaybackException getPlayerError()
    Description copied from interface: Player
    Returns the error that caused playback to fail. This is the same error that will have been - reported via Player.EventListener.onPlayerError(ExoPlaybackException) at the time of failure. It + reported via Player.Listener.onPlayerError(ExoPlaybackException) at the time of failure. It can be queried using this method until the player is re-prepared.

    Note that this method will always return null if Player.getPlaybackState() is not @@ -1263,7 +1263,7 @@ public Returns:

    The error, or null.
    See Also:
    -
    Player.EventListener.onPlayerError(ExoPlaybackException)
    +
    Player.Listener.onPlayerError(ExoPlaybackException)
  • @@ -1297,7 +1297,7 @@ public Returns:
    Whether playback will proceed when ready.
    See Also:
    -
    Player.EventListener.onPlayWhenReadyChanged(boolean, int)
    +
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    @@ -1330,7 +1330,7 @@ public Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the player to the default, which means there is no speed or pitch adjustment. -

    Playback parameters changes may cause the player to buffer. Player.EventListener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently +

    Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently active playback parameters change.

    Parameters:
    @@ -1349,7 +1349,7 @@ public Returns the currently active playback parameters.
    See Also:
    -
    Player.EventListener.onPlaybackParametersChanged(PlaybackParameters)
    +
    Player.Listener.onPlaybackParametersChanged(PlaybackParameters)
    @@ -1404,7 +1404,7 @@ public int getRepeatMode()
    Returns:
    The current repeat mode.
    See Also:
    -
    Player.EventListener.onRepeatModeChanged(int)
    +
    Player.Listener.onRepeatModeChanged(int)
    @@ -1434,7 +1434,7 @@ public int getRepeatMode()
    Returns whether shuffling of windows is enabled.
    See Also:
    -
    Player.EventListener.onShuffleModeEnabledChanged(boolean)
    +
    Player.Listener.onShuffleModeEnabledChanged(boolean)
    @@ -1453,7 +1453,7 @@ public int getRepeatMode() components that is not assigned any selected tracks.
    See Also:
    -
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    @@ -1468,7 +1468,7 @@ public int getRepeatMode()
    Returns the available track groups.
    See Also:
    -
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    @@ -1491,7 +1491,7 @@ public int getRepeatMode() rather than being timed (or dynamic) metadata, which is represented within a metadata track.
    See Also:
    -
    Player.EventListener.onStaticMetadataChanged(List)
    +
    Player.Listener.onStaticMetadataChanged(List)
    @@ -1507,7 +1507,7 @@ public int getRepeatMode() supported.

    This MediaMetadata is a combination of the MediaItem.mediaMetadata and the - static and dynamic metadata sourced from Player.EventListener.onStaticMetadataChanged(List) and + static and dynamic metadata sourced from Player.Listener.onStaticMetadataChanged(List) and MetadataOutput.onMetadata(Metadata). @@ -1522,7 +1522,7 @@ public int getRepeatMode()

    Returns the current Timeline. Never null, but may be empty.
    See Also:
    -
    Player.EventListener.onTimelineChanged(Timeline, int)
    +
    Player.Listener.onTimelineChanged(Timeline, int)
    @@ -1644,7 +1644,7 @@ public int getRepeatMode()
    Returns:
    Whether the player is currently loading the source.
    See Also:
    -
    Player.EventListener.onIsLoadingChanged(boolean)
    +
    Player.Listener.onIsLoadingChanged(boolean)
    @@ -1852,7 +1852,7 @@ public int getRepeatMode()
    This method is not supported and returns VideoSize.UNKNOWN.
    See Also:
    -
    VideoListener.onVideoSizeChanged(VideoSize)
    +
    Player.Listener.onVideoSizeChanged(VideoSize)
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/cast/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/cast/package-frame.html deleted file mode 100644 index 974b81ae75..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/cast/package-frame.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.cast (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.cast

    - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/cronet/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/cronet/package-frame.html deleted file mode 100644 index 7a427fc16c..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/cronet/package-frame.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.cronet (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.cronet

    - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ffmpeg/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ffmpeg/package-frame.html deleted file mode 100644 index 310bb3b883..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ffmpeg/package-frame.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.ffmpeg (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.ffmpeg

    -
    -

    Classes

    - -

    Exceptions

    - -
    - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/flac/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/flac/package-frame.html deleted file mode 100644 index d39e1b82d7..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/flac/package-frame.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.flac (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.flac

    -
    -

    Classes

    - -

    Exceptions

    - -

    Annotation Types

    - -
    - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-frame.html deleted file mode 100644 index 83763e3e0b..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/gvr/package-frame.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.gvr (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.gvr

    -
    -

    Classes

    - -
    - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html index 7a7b2d04ff..6f62a32488 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.html @@ -342,39 +342,25 @@ implements clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait - - @@ -645,6 +631,8 @@ public com.google.ads.interactivemedia.v3.api.AdDisplayContainer getAd
    Specified by:
    onTimelineChanged in interface Player.EventListener
    +
    Specified by:
    +
    onTimelineChanged in interface Player.Listener
    Parameters:
    timeline - The latest timeline. Never null, but may be empty.
    reason - The Player.TimelineChangeReason responsible for this timeline change.
    @@ -673,6 +661,8 @@ public com.google.ads.interactivemedia.v3.api.AdDisplayContainer getAd
    Specified by:
    onPositionDiscontinuity in interface Player.EventListener
    +
    Specified by:
    +
    onPositionDiscontinuity in interface Player.Listener
    Parameters:
    oldPosition - The position before the discontinuity.
    newPosition - The position after the discontinuity.
    @@ -695,6 +685,8 @@ public com.google.ads.interactivemedia.v3.api.AdDisplayContainer getAd
    Specified by:
    onShuffleModeEnabledChanged in interface Player.EventListener
    +
    Specified by:
    +
    onShuffleModeEnabledChanged in interface Player.Listener
    Parameters:
    shuffleModeEnabled - Whether shuffling of windows is enabled.
    @@ -716,6 +708,8 @@ public com.google.ads.interactivemedia.v3.api.AdDisplayContainer getAd
    Specified by:
    onRepeatModeChanged in interface Player.EventListener
    +
    Specified by:
    +
    onRepeatModeChanged in interface Player.Listener
    Parameters:
    repeatMode - The Player.RepeatMode used for playback.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/ima/package-frame.html deleted file mode 100644 index 19db2d9ee9..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/ima/package-frame.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.ima (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.ima

    - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.JobDispatcherSchedulerService.html b/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.JobDispatcherSchedulerService.html deleted file mode 100644 index a6b1755226..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.JobDispatcherSchedulerService.html +++ /dev/null @@ -1,428 +0,0 @@ - - - - -JobDispatcherScheduler.JobDispatcherSchedulerService (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class JobDispatcherScheduler.JobDispatcherSchedulerService

    -
    -
    - -
    -
      -
    • -
      -
      All Implemented Interfaces:
      -
      ComponentCallbacks, ComponentCallbacks2
      -
      -
      -
      Enclosing class:
      -
      JobDispatcherScheduler
      -
      -
      -
      public static final class JobDispatcherScheduler.JobDispatcherSchedulerService
      -extends com.firebase.jobdispatcher.JobService
      -
      A JobService that starts the target service if the requirements are met.
      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          JobDispatcherSchedulerService

          -
          public JobDispatcherSchedulerService()
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          onStartJob

          -
          public boolean onStartJob​(com.firebase.jobdispatcher.JobParameters params)
          -
          -
          Specified by:
          -
          onStartJob in class com.firebase.jobdispatcher.JobService
          -
          -
        • -
        - - - -
          -
        • -

          onStopJob

          -
          public boolean onStopJob​(com.firebase.jobdispatcher.JobParameters params)
          -
          -
          Specified by:
          -
          onStopJob in class com.firebase.jobdispatcher.JobService
          -
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.html b/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.html deleted file mode 100644 index 4484175ff5..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.html +++ /dev/null @@ -1,448 +0,0 @@ - - - - -JobDispatcherScheduler (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class JobDispatcherScheduler

    -
    -
    -
      -
    • java.lang.Object
    • -
    • -
        -
      • com.google.android.exoplayer2.ext.jobdispatcher.JobDispatcherScheduler
      • -
      -
    • -
    -
    -
      -
    • -
      -
      All Implemented Interfaces:
      -
      Scheduler
      -
      -
      -
      @Deprecated
      -public final class JobDispatcherScheduler
      -extends Object
      -implements Scheduler
      -
      Deprecated. -
      Use com.google.android.exoplayer2.ext.workmanager.WorkManagerScheduler or PlatformScheduler.
      -
      -
      A Scheduler that uses FirebaseJobDispatcher. To use this scheduler, you must add - JobDispatcherScheduler.JobDispatcherSchedulerService to your manifest: - -
      - <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
      - <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
      -
      - <service
      -     android:name="com.google.android.exoplayer2.ext.jobdispatcher.JobDispatcherScheduler$JobDispatcherSchedulerService"
      -     android:exported="false">
      -   <intent-filter>
      -     <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
      -   </intent-filter>
      - </service>
      - 
      - -

      This Scheduler uses Google Play services but does not do any availability checks. Any uses - should be guarded with a call to - GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)

      -
      -
      See Also:
      -
      GoogleApiAvailability
      -
      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          JobDispatcherScheduler

          -
          public JobDispatcherScheduler​(Context context,
          -                              String jobTag)
          -
          Deprecated.
          -
          -
          Parameters:
          -
          context - A context.
          -
          jobTag - A tag for jobs scheduled by this instance. If the same tag was used by a previous - instance, anything scheduled by the previous instance will be canceled by this instance if - schedule(Requirements, String, String) or cancel() are called.
          -
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          schedule

          -
          public boolean schedule​(Requirements requirements,
          -                        String servicePackage,
          -                        String serviceAction)
          -
          Deprecated.
          -
          Description copied from interface: Scheduler
          -
          Schedules a service to be started in the foreground when some Requirements are met. - Anything that was previously scheduled will be canceled. - -

          The service to be started must be declared in the manifest of servicePackage with an - intent filter containing serviceAction. Note that when started with - serviceAction, the service must call Service.startForeground(int, Notification) to - make itself a foreground service, as documented by ContextWrapper.startForegroundService(Intent).

          -
          -
          Specified by:
          -
          schedule in interface Scheduler
          -
          Parameters:
          -
          requirements - The requirements.
          -
          servicePackage - The package name.
          -
          serviceAction - The action with which the service will be started.
          -
          Returns:
          -
          Whether scheduling was successful.
          -
          -
        • -
        - - - -
          -
        • -

          cancel

          -
          public boolean cancel()
          -
          Deprecated.
          -
          Description copied from interface: Scheduler
          -
          Cancels anything that was previously scheduled, or else does nothing.
          -
          -
          Specified by:
          -
          cancel in interface Scheduler
          -
          Returns:
          -
          Whether cancellation was successful.
          -
          -
        • -
        - - - -
          -
        • -

          getSupportedRequirements

          -
          public Requirements getSupportedRequirements​(Requirements requirements)
          -
          Deprecated.
          -
          Description copied from interface: Scheduler
          -
          Checks whether this Scheduler supports the provided Requirements. If all of the - requirements are supported then the same Requirements instance is returned. If not then - a new instance is returned containing the subset of the requirements that are supported.
          -
          -
          Specified by:
          -
          getSupportedRequirements in interface Scheduler
          -
          Parameters:
          -
          requirements - The requirements to check.
          -
          Returns:
          -
          The supported requirements.
          -
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-frame.html deleted file mode 100644 index 1841ddf618..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-frame.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.jobdispatcher (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.jobdispatcher

    - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-summary.html deleted file mode 100644 index db446baf10..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-summary.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.jobdispatcher (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - -
    -

    Package com.google.android.exoplayer2.ext.jobdispatcher

    -
    -
    - -
    - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-tree.html deleted file mode 100644 index a02d966bb2..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/jobdispatcher/package-tree.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.jobdispatcher Class Hierarchy (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - -
    -

    Hierarchy For Package com.google.android.exoplayer2.ext.jobdispatcher

    -Package Hierarchies: - -
    -
    -

    Class Hierarchy

    - -
    - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/package-frame.html deleted file mode 100644 index 4839b441bd..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/leanback/package-frame.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - -com.google.android.exoplayer2.ext.leanback (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.ext.leanback

    -
    -

    Classes

    - -
    - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html b/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html index cd2828a878..96a9249368 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html +++ b/docs/doc/reference/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.html @@ -553,7 +553,8 @@ extends androidx.media2.common.SessionPlayer diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/Action.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/Action.html index 9b42fdd025..9214b85333 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/Action.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/Action.html @@ -323,7 +323,7 @@ extends static class  Action.WaitForIsLoading -
    Waits for a specified loading state, returning either immediately or after a call to Player.EventListener.onIsLoadingChanged(boolean).
    +
    Waits for a specified loading state, returning either immediately or after a call to Player.Listener.onIsLoadingChanged(boolean).
    @@ -344,7 +344,7 @@ extends static class  Action.WaitForPlaybackState -
    Waits for a specified playback state, returning either immediately or after a call to Player.EventListener.onPlaybackStateChanged(int).
    +
    Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
    @@ -352,14 +352,14 @@ extends Action.WaitForPlayWhenReady
    Waits for a specified playWhenReady value, returning either immediately or after a call to - Player.EventListener.onPlayWhenReadyChanged(boolean, int).
    + Player.Listener.onPlayWhenReadyChanged(boolean, int). static class  Action.WaitForPositionDiscontinuity -
    Waits for Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, + @@ -367,7 +367,7 @@ extends static class  Action.WaitForTimelineChanged - + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.html index 10d5f203a3..5dcdeef1a5 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.html @@ -133,7 +133,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    org.junit.rules.TestRule

    -
    public final class AdditionalFailureInfo
    +
    @RequiresApi(19)
    +public final class AdditionalFailureInfo
     extends Object
     implements org.junit.rules.TestRule
    A JUnit Rule that attaches additional info to any errors/exceptions thrown by the test. diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.html deleted file mode 100644 index e3386fcb5a..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - -AutoAdvancingFakeClock (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class AutoAdvancingFakeClock

    -
    -
    - -
    -
      -
    • -
      -
      All Implemented Interfaces:
      -
      Clock
      -
      -
      -
      public final class AutoAdvancingFakeClock
      -extends FakeClock
      -
      FakeClock extension which automatically advances time whenever an empty message is - enqueued at a future time. - -

      The clock time is advanced to the time of enqueued empty messages. The first Handler sending - messages at a future time will be allowed to advance time to ensure there is only one primary - time source at a time. This should usually be the Handler of the internal playback loop. You can - reset the handler so that the next Handler that sends messages at a - future time becomes the primary time source.

      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          AutoAdvancingFakeClock

          -
          public AutoAdvancingFakeClock()
          -
          Creates the auto-advancing clock with an initial time of 0.
          -
        • -
        - - - -
          -
        • -

          AutoAdvancingFakeClock

          -
          public AutoAdvancingFakeClock​(long initialTimeMs)
          -
          Creates the auto-advancing clock.
          -
          -
          Parameters:
          -
          initialTimeMs - The initial time of the clock in milliseconds.
          -
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          addHandlerMessageAtTime

          -
          protected boolean addHandlerMessageAtTime​(HandlerWrapper handler,
          -                                          int message,
          -                                          long timeMs)
          -
          Description copied from class: FakeClock
          -
          Adds an empty handler message to list of pending messages.
          -
          -
          Overrides:
          -
          addHandlerMessageAtTime in class FakeClock
          -
          -
        • -
        - - - -
          -
        • -

          resetHandler

          -
          public void resetHandler()
          -
          Resets the internal handler, so that this clock can later be used with another handler.
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/DataSourceContractTest.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/DataSourceContractTest.html index 7af58966f8..bbcc7308ce 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/DataSourceContractTest.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/DataSourceContractTest.html @@ -129,19 +129,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    • -
      public abstract class DataSourceContractTest
      +
      @RequiresApi(19)
      +public abstract class DataSourceContractTest
       extends Object
      A collection of contract tests for DataSource implementations. -

      All these tests should pass for all implementations - behaviour specific to only a subset of - implementations should be tested elsewhere. -

      Subclasses should only include the logic necessary to construct the DataSource and allow it to successfully read data. They shouldn't include any new @Test methods - implementation-specific tests should be in a separate class. -

      If one of these tests fails for a particular DataSource implementation, that's a bug - in the implementation. The test should be overridden in the subclass and annotated Ignore, with a link to an issue to track fixing the implementation and un-ignoring the test.

      +

      Most implementations should pass all these tests. If necessary, subclasses can disable tests + by overriding the @Test method with a no-op implementation. It's recommended (but + not required) to also annotate this @Ignore so that JUnit correclty reports the + test as skipped/ignored instead of passing.

    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html index 001386b238..593d9bcbe5 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.html @@ -218,7 +218,7 @@ implements void assertMediaItemsTransitionedSame​(MediaItem... mediaItems) -
    Asserts that the media items reported by Player.EventListener.onMediaItemTransition(MediaItem, int) are the same as the provided media +
    Asserts that the media items reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided media items.
    @@ -226,14 +226,14 @@ implements void assertMediaItemsTransitionReasonsEqual​(Integer... reasons) -
    Asserts that the media item transition reasons reported by Player.EventListener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
    +
    Asserts that the media item transition reasons reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
    void assertNoPositionDiscontinuities() -
    Asserts that Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, + @@ -241,7 +241,7 @@ implements void assertPlaybackStatesEqual​(Integer... states) -
    Asserts that the playback states reported by Player.EventListener.onPlaybackStateChanged(int) are equal to the provided playback states.
    +
    Asserts that the playback states reported by Player.Listener.onPlaybackStateChanged(int) are equal to the provided playback states.
    @@ -255,7 +255,7 @@ implements void assertPositionDiscontinuityReasonsEqual​(Integer... discontinuityReasons) -
    Asserts that the discontinuity reasons reported by Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are +
    Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are equal to the provided values.
    @@ -263,7 +263,7 @@ implements void assertTimelineChangeReasonsEqual​(Integer... reasons) -
    Asserts that the timeline change reasons reported by Player.EventListener.onTimelineChanged(Timeline, int) are equal to the provided timeline change +
    Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change reasons.
    @@ -271,7 +271,7 @@ implements void assertTimelinesSame​(Timeline... timelines) -
    Asserts that the timelines reported by Player.EventListener.onTimelineChanged(Timeline, int) +
    Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) are the same to the provided timelines.
    @@ -279,7 +279,7 @@ implements void assertTrackGroupsEqual​(TrackGroupArray trackGroupArray) -
    Asserts that the last track group array reported by Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided +
    Asserts that the last track group array reported by Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided track group array.
    @@ -375,39 +375,25 @@ implements clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    - - @@ -540,7 +526,7 @@ implements

    assertTimelinesSame

    public void assertTimelinesSame​(Timeline... timelines)
    -
    Asserts that the timelines reported by Player.EventListener.onTimelineChanged(Timeline, int) +
    Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) are the same to the provided timelines. This assert differs from testing equality by not comparing period ids which may be different due to id mapping of child source period ids.
    @@ -556,7 +542,7 @@ implements

    assertTimelineChangeReasonsEqual

    public void assertTimelineChangeReasonsEqual​(Integer... reasons)
    -
    Asserts that the timeline change reasons reported by Player.EventListener.onTimelineChanged(Timeline, int) are equal to the provided timeline change +
    Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change reasons.
    @@ -567,7 +553,7 @@ implements

    assertMediaItemsTransitionedSame

    public void assertMediaItemsTransitionedSame​(MediaItem... mediaItems)
    -
    Asserts that the media items reported by Player.EventListener.onMediaItemTransition(MediaItem, int) are the same as the provided media +
    Asserts that the media items reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided media items.
    Parameters:
    @@ -582,7 +568,7 @@ implements

    assertMediaItemsTransitionReasonsEqual

    public void assertMediaItemsTransitionReasonsEqual​(Integer... reasons)
    -
    Asserts that the media item transition reasons reported by Player.EventListener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
    +
    Asserts that the media item transition reasons reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
    Parameters:
    reasons - A list of expected transition reasons.
    @@ -596,7 +582,7 @@ implements

    assertPlaybackStatesEqual

    public void assertPlaybackStatesEqual​(Integer... states)
    -
    Asserts that the playback states reported by Player.EventListener.onPlaybackStateChanged(int) are equal to the provided playback states.
    +
    Asserts that the playback states reported by Player.Listener.onPlaybackStateChanged(int) are equal to the provided playback states.
    @@ -606,7 +592,7 @@ implements

    assertTrackGroupsEqual

    public void assertTrackGroupsEqual​(TrackGroupArray trackGroupArray)
    -
    Asserts that the last track group array reported by Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided +
    Asserts that the last track group array reported by Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided track group array.
    Parameters:
    @@ -621,7 +607,7 @@ implements

    assertNoPositionDiscontinuities

    public void assertNoPositionDiscontinuities()
    -
    Asserts that Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, + @@ -632,7 +618,7 @@ implements

    assertPositionDiscontinuityReasonsEqual

    public void assertPositionDiscontinuityReasonsEqual​(Integer... discontinuityReasons)
    -
    Asserts that the discontinuity reasons reported by Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are +
    Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are equal to the provided values.
    Parameters:
    @@ -677,6 +663,8 @@ implements Specified by:
    onTimelineChanged in interface Player.EventListener
    +
    Specified by:
    +
    onTimelineChanged in interface Player.Listener
    Parameters:
    timeline - The latest timeline. Never null, but may be empty.
    reason - The Player.TimelineChangeReason responsible for this timeline change.
    @@ -705,6 +693,8 @@ implements Specified by:
    onMediaItemTransition in interface Player.EventListener
    +
    Specified by:
    +
    onMediaItemTransition in interface Player.Listener
    Parameters:
    mediaItem - The MediaItem. May be null if the playlist becomes empty.
    reason - The reason for the transition.
    @@ -727,6 +717,8 @@ implements Specified by:
    onTracksChanged in interface Player.EventListener
    +
    Specified by:
    +
    onTracksChanged in interface Player.Listener
    Parameters:
    trackGroups - The available tracks. Never null, but may be of length zero.
    trackSelections - The selected tracks. Never null, but may contain null elements. A @@ -752,6 +744,8 @@ implements Specified by:
    onPlaybackStateChanged in interface Player.EventListener
    +
    Specified by:
    +
    onPlaybackStateChanged in interface Player.Listener
    Parameters:
    playbackState - The new playback state.
    @@ -773,6 +767,8 @@ implements Specified by:
    onPlayerError in interface Player.EventListener
    +
    Specified by:
    +
    onPlayerError in interface Player.Listener
    Parameters:
    error - The error.
    @@ -800,6 +796,8 @@ implements Specified by:
    onPositionDiscontinuity in interface Player.EventListener
    +
    Specified by:
    +
    onPositionDiscontinuity in interface Player.Listener
    Parameters:
    oldPosition - The position before the discontinuity.
    newPosition - The position after the discontinuity.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.Builder.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.Builder.html new file mode 100644 index 0000000000..3a3ba9ce06 --- /dev/null +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.Builder.html @@ -0,0 +1,361 @@ + + + + +FakeExoMediaDrm.Builder (ExoPlayer library) + + + + + + + + + + + + + +
    + +
    + +
    +
    + +

    Class FakeExoMediaDrm.Builder

    +
    +
    +
      +
    • java.lang.Object
    • +
    • +
        +
      • com.google.android.exoplayer2.testutil.FakeExoMediaDrm.Builder
      • +
      +
    • +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + + + diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html index 42e69af5ea..795140981e 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.html @@ -25,7 +25,7 @@ catch(err) { } //--> -var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10}; +var data = {"i0":10,"i1":10,"i2":10,"i3":10,"i4":10,"i5":10,"i6":10,"i7":10,"i8":10,"i9":10,"i10":10,"i11":10,"i12":10,"i13":10,"i14":10,"i15":10,"i16":10,"i17":10,"i18":10,"i19":10,"i20":10,"i21":10,"i22":10}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],8:["t4","Concrete Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -133,7 +133,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
    ExoMediaDrm

    -
    public final class FakeExoMediaDrm
    +
    @RequiresApi(29)
    +public final class FakeExoMediaDrm
     extends Object
     implements ExoMediaDrm
    A fake implementation of ExoMediaDrm for use in tests. @@ -163,6 +164,13 @@ implements static class  +FakeExoMediaDrm.Builder + +
    Builder for FakeExoMediaDrm instances.
    + + + +static class  FakeExoMediaDrm.LicenseServer
    An license server implementation to interact with FakeExoMediaDrm.
    @@ -219,6 +227,11 @@ implements Value for use with the Map returned from queryKeyStatus(byte[]).
    + +static ImmutableList<Byte> +VALID_PROVISION_RESPONSE +  +
    - + void setPropertyString​(String propertyName, String value) @@ -427,7 +450,7 @@ implements Sets the value of a string property.
    - + void triggerEvent​(Predicate<byte[]> sessionIdPredicate, int event, @@ -472,6 +495,15 @@ implements ExoMediaDrm.ProvisionRequest FAKE_PROVISION_REQUEST
    + + + +
      +
    • +

      VALID_PROVISION_RESPONSE

      +
      public static final ImmutableList<Byte> VALID_PROVISION_RESPONSE
      +
    • +
    @@ -530,9 +562,11 @@ implements
  • FakeExoMediaDrm

    -
    public FakeExoMediaDrm()
    -
    +
    @Deprecated
    +public FakeExoMediaDrm()
    +
    Deprecated. + +
  • @@ -541,13 +575,11 @@ implements
  • FakeExoMediaDrm

    -
    public FakeExoMediaDrm​(int maxConcurrentSessions)
    -
    -
    -
    Parameters:
    -
    maxConcurrentSessions - The max number of sessions allowed to be open simultaneously.
    -
    +
    @Deprecated
    +public FakeExoMediaDrm​(int maxConcurrentSessions)
    +
    Deprecated. + +
  • @@ -988,7 +1020,7 @@ public  -
      +
      • triggerEvent

        public void triggerEvent​(Predicate<byte[]> sessionIdPredicate,
        @@ -1001,6 +1033,17 @@ public 
        +
        +
        +
          +
        • +

          resetProvisioning

          +
          public void resetProvisioning()
          +
          Resets the provisioning state of this instance, so it requires provisionsRequired (possibly zero) provision operations + before it's operational again.
          +
        • +
      diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/StubExoPlayer.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/StubExoPlayer.html index 197db674a3..5a0d17a2e9 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/StubExoPlayer.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/StubExoPlayer.html @@ -1264,7 +1264,7 @@ public int getPlaybackState()
      Returns:
      The current playback state.
      See Also:
      -
      Player.EventListener.onPlaybackStateChanged(int)
      +
      Player.Listener.onPlaybackStateChanged(int)
    @@ -1285,7 +1285,7 @@ public int getPlaybackSuppressionReason()
    Returns:
    The current playback suppression reason.
    See Also:
    -
    Player.EventListener.onPlaybackSuppressionReasonChanged(int)
    +
    Player.Listener.onPlaybackSuppressionReasonChanged(int)
    @@ -1298,7 +1298,7 @@ public int getPlaybackSuppressionReason()
    public ExoPlaybackException getPlayerError()
    Description copied from interface: Player
    Returns the error that caused playback to fail. This is the same error that will have been - reported via Player.EventListener.onPlayerError(ExoPlaybackException) at the time of failure. It + reported via Player.Listener.onPlayerError(ExoPlaybackException) at the time of failure. It can be queried using this method until the player is re-prepared.

    Note that this method will always return null if Player.getPlaybackState() is not @@ -1309,7 +1309,7 @@ public int getPlaybackSuppressionReason()

    Returns:
    The error, or null.
    See Also:
    -
    Player.EventListener.onPlayerError(ExoPlaybackException)
    +
    Player.Listener.onPlayerError(ExoPlaybackException)
    @@ -1697,7 +1697,7 @@ public void prepare​(Description copied from interface: Player
    Returns the player's currently available Player.Commands. -

    The returned Player.Commands are not updated when available commands change. Use Player.EventListener.onAvailableCommandsChanged(Commands) to get an update when the available commands +

    The returned Player.Commands are not updated when available commands change. Use Player.Listener.onAvailableCommandsChanged(Commands) to get an update when the available commands change.

    Executing a command that is not available (for example, calling Player.next() if Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM is unavailable) will neither throw an exception nor generate @@ -1711,7 +1711,7 @@ public void prepare​(Returns:

    The currently available Player.Commands.
    See Also:
    -
    Player.EventListener.onAvailableCommandsChanged(com.google.android.exoplayer2.Player.Commands)
    +
    Player.Listener.onAvailableCommandsChanged(com.google.android.exoplayer2.Player.Commands)
    @@ -1749,7 +1749,7 @@ public void prepare​(Returns:
    Whether playback will proceed when ready.
    See Also:
    -
    Player.EventListener.onPlayWhenReadyChanged(boolean, int)
    +
    Player.Listener.onPlayWhenReadyChanged(boolean, int)
    @@ -1786,7 +1786,7 @@ public void prepare​(Returns:
    The current repeat mode.
    See Also:
    -
    Player.EventListener.onRepeatModeChanged(int)
    +
    Player.Listener.onRepeatModeChanged(int)
    @@ -1837,7 +1837,7 @@ public void prepare​(Specified by:
    getShuffleModeEnabled in interface Player
    See Also:
    -
    Player.EventListener.onShuffleModeEnabledChanged(boolean)
    +
    Player.Listener.onShuffleModeEnabledChanged(boolean)
    @@ -1856,7 +1856,7 @@ public void prepare​(Returns:
    Whether the player is currently loading the source.
    See Also:
    -
    Player.EventListener.onIsLoadingChanged(boolean)
    +
    Player.Listener.onIsLoadingChanged(boolean)
    @@ -1891,7 +1891,7 @@ public void prepare​(Attempts to set the playback parameters. Passing PlaybackParameters.DEFAULT resets the player to the default, which means there is no speed or pitch adjustment. -

    Playback parameters changes may cause the player to buffer. Player.EventListener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently +

    Playback parameters changes may cause the player to buffer. Player.Listener.onPlaybackParametersChanged(PlaybackParameters) will be called whenever the currently active playback parameters change.

    Specified by:
    @@ -1914,7 +1914,7 @@ public void prepare​(Specified by:
    getPlaybackParameters in interface Player
    See Also:
    -
    Player.EventListener.onPlaybackParametersChanged(PlaybackParameters)
    +
    Player.Listener.onPlaybackParametersChanged(PlaybackParameters)
    @@ -2065,7 +2065,7 @@ public Specified by:
    getCurrentTrackGroups in interface Player
    See Also:
    -
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    @@ -2086,7 +2086,7 @@ public Specified by:
    getCurrentTrackSelections in interface Player
    See Also:
    -
    Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    +
    Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray)
    @@ -2111,7 +2111,7 @@ public Specified by:
    getCurrentStaticMetadata in interface Player
    See Also:
    -
    Player.EventListener.onStaticMetadataChanged(List)
    +
    Player.Listener.onStaticMetadataChanged(List)
    @@ -2127,7 +2127,7 @@ public MediaMetadata is a combination of the MediaItem.mediaMetadata and the - static and dynamic metadata sourced from Player.EventListener.onStaticMetadataChanged(List) and + static and dynamic metadata sourced from Player.Listener.onStaticMetadataChanged(List) and MetadataOutput.onMetadata(Metadata).
    Specified by:
    @@ -2148,7 +2148,7 @@ public Specified by:
    getCurrentTimeline in interface Player
    See Also:
    -
    Player.EventListener.onTimelineChanged(Timeline, int)
    +
    Player.Listener.onTimelineChanged(Timeline, int)
    @@ -2445,7 +2445,10 @@ public SurfaceHolder surfaceHolder)
    Description copied from interface: Player
    Sets the SurfaceHolder that holds the Surface onto which video will be - rendered. The player will track the lifecycle of the surface automatically.
    + rendered. The player will track the lifecycle of the surface automatically. + +

    The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with Player.getApplicationLooper().

    Specified by:
    setVideoSurfaceHolder in interface Player
    @@ -2483,7 +2486,10 @@ public SurfaceView surfaceView)
    Description copied from interface: Player
    Sets the SurfaceView onto which video will be rendered. The player will track the - lifecycle of the surface automatically.
    + lifecycle of the surface automatically. + +

    The thread that calls the SurfaceHolder.Callback methods must be the thread + associated with Player.getApplicationLooper().

    Specified by:
    setVideoSurfaceView in interface Player
    @@ -2521,7 +2527,10 @@ public TextureView textureView)
    Description copied from interface: Player
    Sets the TextureView onto which video will be rendered. The player will track the - lifecycle of the surface automatically.
    + lifecycle of the surface automatically. + +

    The thread that calls the TextureView.SurfaceTextureListener methods must be the + thread associated with Player.getApplicationLooper().

    Specified by:
    setVideoTextureView in interface Player
    @@ -2565,7 +2574,7 @@ public Specified by:
    getVideoSize in interface Player
    See Also:
    -
    VideoListener.onVideoSizeChanged(VideoSize)
    +
    Player.Listener.onVideoSizeChanged(VideoSize)
    @@ -2749,7 +2758,7 @@ public Description copied from interface: ExoPlayer
    Sets whether to pause playback at the end of each media item. -

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.EventListener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    +

    This means the player will pause at the end of each window in the current timeline. Listeners will be informed by a call to Player.Listener.onPlayWhenReadyChanged(boolean, int) with the reason Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM when this happens.

    Specified by:
    setPauseAtEndOfMediaItems in interface ExoPlayer
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-frame.html deleted file mode 100644 index 0cb2aa148b..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-frame.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - -com.google.android.exoplayer2.testutil (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.testutil

    -
    -

    Interfaces

    - -

    Classes

    - -

    Exceptions

    - -
    - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html index 4f09751b78..9f805fe524 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-summary.html @@ -317,7 +317,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.WaitForIsLoading -
    Waits for a specified loading state, returning either immediately or after a call to Player.EventListener.onIsLoadingChanged(boolean).
    +
    Waits for a specified loading state, returning either immediately or after a call to Player.Listener.onIsLoadingChanged(boolean).
    @@ -335,27 +335,27 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Action.WaitForPlaybackState -
    Waits for a specified playback state, returning either immediately or after a call to Player.EventListener.onPlaybackStateChanged(int).
    +
    Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
    Action.WaitForPlayWhenReady
    Waits for a specified playWhenReady value, returning either immediately or after a call to - Player.EventListener.onPlayWhenReadyChanged(boolean, int).
    + Player.Listener.onPlayWhenReadyChanged(boolean, int). Action.WaitForPositionDiscontinuity -
    Waits for Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, + Action.WaitForTimelineChanged - + @@ -625,195 +625,201 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +FakeExoMediaDrm.Builder + +
    Builder for FakeExoMediaDrm instances.
    + + + FakeExoMediaDrm.LicenseServer
    An license server implementation to interact with FakeExoMediaDrm.
    - + FakeExtractorInput
    A fake ExtractorInput capable of simulating various scenarios.
    - + FakeExtractorInput.Builder
    Builder of FakeExtractorInput instances.
    - + FakeExtractorOutput - + FakeMediaChunk - + FakeMediaChunkIterator - + FakeMediaClockRenderer
    Fake abstract Renderer which is also a MediaClock.
    - + FakeMediaPeriod
    Fake MediaPeriod that provides tracks from the given TrackGroupArray.
    - + FakeMediaSource
    Fake MediaSource that provides a given timeline.
    - + FakeMediaSource.InitialTimeline
    A forwarding timeline to provide an initial timeline for fake multi window sources.
    - + FakeRenderer
    Fake Renderer that supports any format with the matching track type.
    - + FakeSampleStream
    Fake SampleStream that outputs a given Format and any amount of items.
    - + FakeSampleStream.FakeSampleStreamItem - + FakeShuffleOrder
    Fake ShuffleOrder which returns a reverse order.
    - + FakeTimeline
    Fake Timeline which can be setup to return custom FakeTimeline.TimelineWindowDefinitions.
    - + FakeTimeline.TimelineWindowDefinition
    Definition used to define a FakeTimeline.
    - + FakeTrackOutput
    A fake TrackOutput.
    - + FakeTrackSelection
    A fake ExoTrackSelection that only returns 1 fixed track, and allows querying the number of calls to its methods.
    - + FakeTrackSelector - + FakeVideoRenderer - + HostActivity
    A host activity for performing playback tests.
    - + HttpDataSourceTestEnv
    A JUnit Rule that creates test resources for HttpDataSource contract tests.
    - + MediaPeriodAsserts
    Assertion methods for MediaPeriod.
    - + MediaSourceTestRunner
    A runner for MediaSource tests.
    - + NoUidTimeline
    A timeline which wraps another timeline and overrides all window and period uids to 0.
    - + StubExoPlayer
    An abstract ExoPlayer implementation that throws UnsupportedOperationException from every method.
    - + TestExoPlayerBuilder
    A builder of SimpleExoPlayer instances for testing.
    - + TestUtil
    Utility methods for tests.
    - + TimelineAsserts
    Assertion methods for Timeline.
    - + WebServerDispatcher
    A Dispatcher for MockWebServer that allows per-path customisation of the static data served.
    - + WebServerDispatcher.Resource
    A resource served by WebServerDispatcher.
    - + WebServerDispatcher.Resource.Builder diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html index b52a6ad3ca..1962b15df7 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/package-tree.html @@ -252,6 +252,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment
  • com.google.android.exoplayer2.testutil.FakeDataSource.Factory (implements com.google.android.exoplayer2.upstream.DataSource.Factory)
  • com.google.android.exoplayer2.testutil.FakeExoMediaDrm (implements com.google.android.exoplayer2.drm.ExoMediaDrm)
  • +
  • com.google.android.exoplayer2.testutil.FakeExoMediaDrm.Builder
  • com.google.android.exoplayer2.testutil.FakeExoMediaDrm.LicenseServer (implements com.google.android.exoplayer2.drm.MediaDrmCallback)
  • com.google.android.exoplayer2.testutil.FakeExtractorInput (implements com.google.android.exoplayer2.extractor.ExtractorInput)
  • com.google.android.exoplayer2.testutil.FakeExtractorInput.Builder
  • diff --git a/docs/doc/reference/com/google/android/exoplayer2/testutil/truth/SpannedSubject.Colored.html b/docs/doc/reference/com/google/android/exoplayer2/testutil/truth/SpannedSubject.Colored.html index ee90c8f0c0..e069ed316f 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/testutil/truth/SpannedSubject.Colored.html +++ b/docs/doc/reference/com/google/android/exoplayer2/testutil/truth/SpannedSubject.Colored.html @@ -177,7 +177,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

    -
    public final class EGLSurfaceTexture
    +
    @RequiresApi(17)
    +public final class EGLSurfaceTexture
     extends Object
     implements SurfaceTexture.OnFrameAvailableListener, Runnable
    Generates a SurfaceTexture using EGL/GLES functions.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/EventLogger.html b/docs/doc/reference/com/google/android/exoplayer2/util/EventLogger.html index a988eb94e9..0b9bc33594 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/EventLogger.html +++ b/docs/doc/reference/com/google/android/exoplayer2/util/EventLogger.html @@ -1411,7 +1411,7 @@ implements Player.EventListener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException) is the appropriate place to implement such behavior. This + Player.Listener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException) is the appropriate place to implement such behavior. This method is called to provide the application with an opportunity to log the error if it wishes to do so.
    @@ -1587,7 +1587,7 @@ implements Player.EventListener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException) is the appropriate place to implement such behavior. This + Player.Listener.onPlayerError(com.google.android.exoplayer2.ExoPlaybackException) is the appropriate place to implement such behavior. This method is called to provide the application with an opportunity to log the error if it wishes to do so.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/MutableFlags.html b/docs/doc/reference/com/google/android/exoplayer2/util/MutableFlags.html deleted file mode 100644 index c439e2405f..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/util/MutableFlags.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - -MutableFlags (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class MutableFlags

    -
    -
    - -
    -
      -
    • -
      -
      Direct Known Subclasses:
      -
      AnalyticsListener.Events, Player.Events
      -
      -
      -
      public class MutableFlags
      -extends Object
      -
      A set of integer flags. - -

      Intended for usages where the number of flags may exceed 32 and can no longer be represented - by an IntDef.

      -
    • -
    -
    -
    -
      -
    • - -
        -
      • - - -

        Constructor Summary

        - - - - - - - - - - -
        Constructors 
        ConstructorDescription
        MutableFlags() -
        Creates the set of flags.
        -
        -
      • -
      - - -
    • -
    -
    -
    -
      -
    • - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          MutableFlags

          -
          public MutableFlags()
          -
          Creates the set of flags.
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          clear

          -
          public void clear()
          -
          Clears all previously set flags.
          -
        • -
        - - - -
          -
        • -

          add

          -
          public void add​(int flag)
          -
          Adds a flag to the set.
          -
          -
          Parameters:
          -
          flag - The flag to add.
          -
          -
        • -
        - - - -
          -
        • -

          contains

          -
          public boolean contains​(int flag)
          -
          Returns whether the set contains the given flag.
          -
          -
          Parameters:
          -
          flag - The flag.
          -
          Returns:
          -
          Whether the set contains the flag.
          -
          -
        • -
        - - - -
          -
        • -

          containsAny

          -
          public boolean containsAny​(int... flags)
          -
          Returns whether the set contains at least one of the given flags.
          -
          -
          Parameters:
          -
          flags - The flags.
          -
          Returns:
          -
          Whether the set contains at least one of the flags.
          -
          -
        • -
        - - - -
          -
        • -

          size

          -
          public int size()
          -
          Returns the number of flags in this set.
          -
        • -
        - - - -
          -
        • -

          get

          -
          public int get​(int index)
          -
          Returns the flag at the given index.
          -
          -
          Parameters:
          -
          index - The index. Must be between 0 (inclusive) and size() (exclusive).
          -
          Returns:
          -
          The flag at the given index.
          -
          Throws:
          -
          IllegalArgumentException - If index is outside the allowed range.
          -
          -
        • -
        - - - -
          -
        • -

          equals

          -
          public boolean equals​(@Nullable
          -                      Object o)
          -
          -
          Overrides:
          -
          equals in class Object
          -
          -
        • -
        - - - -
          -
        • -

          hashCode

          -
          public int hashCode()
          -
          -
          Overrides:
          -
          hashCode in class Object
          -
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/util/Util.html b/docs/doc/reference/com/google/android/exoplayer2/util/Util.html index 478cb436ca..a2fc34c240 100644 --- a/docs/doc/reference/com/google/android/exoplayer2/util/Util.html +++ b/docs/doc/reference/com/google/android/exoplayer2/util/Util.html @@ -2371,7 +2371,8 @@ public static <T> T[] castNonNullTypeArray​(@Nullable @@ -340,7 +342,9 @@ implements
  • unappliedRotationDegrees

    -
    public final int unappliedRotationDegrees
    +
    @IntRange(from=0L,
    +          to=359L)
    +public final int unappliedRotationDegrees
  • pixelWidthHeightRatio

    -
    public final float pixelWidthHeightRatio
    +
    @FloatRange(from=0.0,
    +            fromInclusive=false)
    +public final float pixelWidthHeightRatio
  • VideoSize

    -
    public VideoSize​(int width,
    +
    public VideoSize​(@IntRange(from=0L)
    +                 int width,
    +                 @IntRange(from=0L)
                      int height)
    Creates a VideoSize without unapplied rotation or anamorphic content.
    @@ -408,9 +416,13 @@ implements
  • VideoSize

    -
    public VideoSize​(int width,
    +
    public VideoSize​(@IntRange(from=0L)
    +                 int width,
    +                 @IntRange(from=0L)
                      int height,
    +                 @IntRange(from=0L,to=359L)
                      int unappliedRotationDegrees,
    +                 @FloatRange(from=0.0,fromInclusive=false)
                      float pixelWidthHeightRatio)
    Creates a VideoSize.
    diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/video/package-frame.html deleted file mode 100644 index 6ae91641d6..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/package-frame.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - -com.google.android.exoplayer2.video (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.video

    - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.html deleted file mode 100644 index 356819f639..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.html +++ /dev/null @@ -1,387 +0,0 @@ - - - - -FrameRotationQueue (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class FrameRotationQueue

    -
    -
    -
      -
    • java.lang.Object
    • -
    • -
        -
      • com.google.android.exoplayer2.video.spherical.FrameRotationQueue
      • -
      -
    • -
    -
    -
      -
    • -
      -
      public final class FrameRotationQueue
      -extends Object
      -
      This class serves multiple purposes: - -
        -
      • Queues the rotation metadata extracted from camera motion track. -
      • Converts the metadata to rotation matrices in OpenGl coordinate system. -
      • Recenters the rotations to componsate the yaw of the initial rotation. -
      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          FrameRotationQueue

          -
          public FrameRotationQueue()
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          setRotation

          -
          public void setRotation​(long timestampUs,
          -                        float[] angleAxis)
          -
          Sets a rotation for a given timestamp.
          -
          -
          Parameters:
          -
          timestampUs - Timestamp of the rotation.
          -
          angleAxis - Angle axis orientation in radians representing the rotation from camera - coordinate system to world coordinate system.
          -
          -
        • -
        - - - -
          -
        • -

          reset

          -
          public void reset()
          -
          Removes all of the rotations and forces rotations to be recentered.
          -
        • -
        - - - -
          -
        • -

          pollRotationMatrix

          -
          public boolean pollRotationMatrix​(float[] matrix,
          -                                  long timestampUs)
          -
          Copies the rotation matrix with the greatest timestamp which is less than or equal to the given - timestamp to matrix. Removes all older rotations and the returned one from the queue. - Does nothing if there is no such rotation.
          -
          -
          Parameters:
          -
          matrix - The rotation matrix.
          -
          timestampUs - The time in microseconds to query the rotation.
          -
          Returns:
          -
          Whether a rotation matrix is copied to matrix.
          -
          -
        • -
        - - - -
          -
        • -

          computeRecenterMatrix

          -
          public static void computeRecenterMatrix​(float[] recenterMatrix,
          -                                         float[] rotationMatrix)
          -
          Computes a recentering matrix from the given angle-axis rotation only accounting for yaw. Roll - and tilt will not be compensated.
          -
          -
          Parameters:
          -
          recenterMatrix - The recenter matrix.
          -
          rotationMatrix - The rotation matrix.
          -
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.DrawMode.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.DrawMode.html deleted file mode 100644 index c163799563..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.DrawMode.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - -Projection.DrawMode (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Annotation Type Projection.DrawMode

    -
    -
    -
    - -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.Mesh.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.Mesh.html deleted file mode 100644 index cc94bfe375..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.Mesh.html +++ /dev/null @@ -1,323 +0,0 @@ - - - - -Projection.Mesh (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class Projection.Mesh

    -
    -
    -
      -
    • java.lang.Object
    • -
    • -
        -
      • com.google.android.exoplayer2.video.spherical.Projection.Mesh
      • -
      -
    • -
    -
    -
      -
    • -
      -
      Enclosing class:
      -
      Projection
      -
      -
      -
      public static final class Projection.Mesh
      -extends Object
      -
      A Mesh associated with the projection scene.
      -
    • -
    -
    -
    - -
    -
    -
      -
    • - - - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          getSubMeshCount

          -
          public int getSubMeshCount()
          -
          Returns the number of sub meshes.
          -
        • -
        - - - -
          -
        • -

          getSubMesh

          -
          public Projection.SubMesh getSubMesh​(int index)
          -
          Returns the SubMesh for the given index.
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.SubMesh.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.SubMesh.html deleted file mode 100644 index 37dc53ae8b..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.SubMesh.html +++ /dev/null @@ -1,427 +0,0 @@ - - - - -Projection.SubMesh (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class Projection.SubMesh

    -
    -
    -
      -
    • java.lang.Object
    • -
    • -
        -
      • com.google.android.exoplayer2.video.spherical.Projection.SubMesh
      • -
      -
    • -
    -
    -
      -
    • -
      -
      Enclosing class:
      -
      Projection
      -
      -
      -
      public static final class Projection.SubMesh
      -extends Object
      -
      The sub mesh associated with the Projection.Mesh.
      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Field Detail

        - - - -
          -
        • -

          VIDEO_TEXTURE_ID

          -
          public static final int VIDEO_TEXTURE_ID
          -
          Texture ID for video frames.
          -
          -
          See Also:
          -
          Constant Field Values
          -
          -
        • -
        - - - -
          -
        • -

          textureId

          -
          public final int textureId
          -
          Texture ID.
          -
        • -
        - - - - - - - -
          -
        • -

          vertices

          -
          public final float[] vertices
          -
          The SubMesh vertices.
          -
        • -
        - - - -
          -
        • -

          textureCoords

          -
          public final float[] textureCoords
          -
          The SubMesh texture coordinates.
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          SubMesh

          -
          public SubMesh​(int textureId,
          -               float[] vertices,
          -               float[] textureCoords,
          -               @DrawMode
          -               int mode)
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          getVertexCount

          -
          public int getVertexCount()
          -
          Returns the SubMesh vertex count.
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.html deleted file mode 100644 index fea673f92b..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/Projection.html +++ /dev/null @@ -1,621 +0,0 @@ - - - - -Projection (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class Projection

    -
    -
    -
      -
    • java.lang.Object
    • -
    • -
        -
      • com.google.android.exoplayer2.video.spherical.Projection
      • -
      -
    • -
    -
    -
      -
    • -
      -
      public final class Projection
      -extends Object
      -
      The projection mesh used with 360/VR videos.
      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Field Detail

        - - - -
          -
        • -

          DRAW_MODE_TRIANGLES

          -
          public static final int DRAW_MODE_TRIANGLES
          -
          Triangle draw mode.
          -
          -
          See Also:
          -
          Constant Field Values
          -
          -
        • -
        - - - -
          -
        • -

          DRAW_MODE_TRIANGLES_STRIP

          -
          public static final int DRAW_MODE_TRIANGLES_STRIP
          -
          Triangle strip draw mode.
          -
          -
          See Also:
          -
          Constant Field Values
          -
          -
        • -
        - - - -
          -
        • -

          DRAW_MODE_TRIANGLES_FAN

          -
          public static final int DRAW_MODE_TRIANGLES_FAN
          -
          Triangle fan draw mode.
          -
          -
          See Also:
          -
          Constant Field Values
          -
          -
        • -
        - - - -
          -
        • -

          TEXTURE_COORDS_PER_VERTEX

          -
          public static final int TEXTURE_COORDS_PER_VERTEX
          -
          Number of position coordinates per vertex.
          -
          -
          See Also:
          -
          Constant Field Values
          -
          -
        • -
        - - - -
          -
        • -

          POSITION_COORDS_PER_VERTEX

          -
          public static final int POSITION_COORDS_PER_VERTEX
          -
          Number of texture coordinates per vertex.
          -
          -
          See Also:
          -
          Constant Field Values
          -
          -
        • -
        - - - -
          -
        • -

          leftMesh

          -
          public final Projection.Mesh leftMesh
          -
          The Mesh corresponding to the left eye.
          -
        • -
        - - - -
          -
        • -

          rightMesh

          -
          public final Projection.Mesh rightMesh
          -
          The Mesh corresponding to the right eye. If singleMesh is true then this mesh is - identical to leftMesh.
          -
        • -
        - - - -
          -
        • -

          stereoMode

          -
          @StereoMode
          -public final int stereoMode
          -
          The stereo mode.
          -
        • -
        - - - -
          -
        • -

          singleMesh

          -
          public final boolean singleMesh
          -
          Whether the left and right mesh are identical.
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Constructor Detail

        - - - -
          -
        • -

          Projection

          -
          public Projection​(Projection.Mesh mesh,
          -                  int stereoMode)
          -
          Creates a Projection with single mesh.
          -
          -
          Parameters:
          -
          mesh - the Mesh for both eyes.
          -
          stereoMode - A C.StereoMode value.
          -
          -
        • -
        - - - -
          -
        • -

          Projection

          -
          public Projection​(Projection.Mesh leftMesh,
          -                  Projection.Mesh rightMesh,
          -                  int stereoMode)
          -
          Creates a Projection with dual mesh. Use Projection(Mesh, int) if there is single mesh - for both eyes.
          -
          -
          Parameters:
          -
          leftMesh - the Mesh corresponding to the left eye.
          -
          rightMesh - the Mesh corresponding to the right eye.
          -
          stereoMode - A C.StereoMode value.
          -
          -
        • -
        -
      • -
      - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          createEquirectangular

          -
          public static Projection createEquirectangular​(@StereoMode
          -                                               int stereoMode)
          -
          Generates a complete sphere equirectangular projection.
          -
          -
          Parameters:
          -
          stereoMode - A C.StereoMode value.
          -
          -
        • -
        - - - -
          -
        • -

          createEquirectangular

          -
          public static Projection createEquirectangular​(float radius,
          -                                               int latitudes,
          -                                               int longitudes,
          -                                               float verticalFovDegrees,
          -                                               float horizontalFovDegrees,
          -                                               @StereoMode
          -                                               int stereoMode)
          -
          Generates an equirectangular projection.
          -
          -
          Parameters:
          -
          radius - Size of the sphere. Must be > 0.
          -
          latitudes - Number of rows that make up the sphere. Must be >= 1.
          -
          longitudes - Number of columns that make up the sphere. Must be >= 1.
          -
          verticalFovDegrees - Total latitudinal degrees that are covered by the sphere. Must be in - (0, 180].
          -
          horizontalFovDegrees - Total longitudinal degrees that are covered by the sphere.Must be - in (0, 360].
          -
          stereoMode - A C.StereoMode value.
          -
          Returns:
          -
          an equirectangular projection.
          -
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.html deleted file mode 100644 index 0239a4adb1..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/ProjectionDecoder.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - -ProjectionDecoder (ExoPlayer library) - - - - - - - - - - - - - -
    - - - - -
    - - - -
    - -

    Class ProjectionDecoder

    -
    -
    -
      -
    • java.lang.Object
    • -
    • -
        -
      • com.google.android.exoplayer2.video.spherical.ProjectionDecoder
      • -
      -
    • -
    -
    -
      -
    • -
      -
      public final class ProjectionDecoder
      -extends Object
      -
      A decoder for the projection mesh. - -

      The mesh boxes parsed are described at - Spherical Video V2 RFC. - -

      The decoder does not perform CRC checks at the moment.

      -
    • -
    -
    -
    - -
    -
    -
      -
    • - -
        -
      • - - -

        Method Detail

        - - - -
          -
        • -

          decode

          -
          @Nullable
          -public static Projection decode​(byte[] projectionData,
          -                                @StereoMode
          -                                int stereoMode)
          -
        • -
        -
      • -
      -
    • -
    -
    -
    - - - - - - - diff --git a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/package-frame.html b/docs/doc/reference/com/google/android/exoplayer2/video/spherical/package-frame.html deleted file mode 100644 index 3a1c727e8f..0000000000 --- a/docs/doc/reference/com/google/android/exoplayer2/video/spherical/package-frame.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - -com.google.android.exoplayer2.video.spherical (ExoPlayer library) - - - - - - - - - - - -

    com.google.android.exoplayer2.video.spherical

    - - - diff --git a/docs/doc/reference/constant-values.html b/docs/doc/reference/constant-values.html index 27eb0e1569..65512765ff 100644 --- a/docs/doc/reference/constant-values.html +++ b/docs/doc/reference/constant-values.html @@ -3397,27 +3397,34 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); 80000 + + +public static final String +E_AC_3_CODEC_STRING +"ec+3" + + public static final int E_AC3_MAX_RATE_BYTES_PER_SECOND 768000 - + public static final int TRUEHD_MAX_RATE_BYTES_PER_SECOND 3062500 - + public static final int TRUEHD_RECHUNK_SAMPLE_COUNT 16 - + public static final int @@ -6713,6 +6720,25 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
  • +
  • + + + + + + + + + + + + + + +
    com.google.android.exoplayer2.source.rtsp.RtspMediaSource 
    Modifier and TypeConstant FieldValue
    + +public static final longDEFAULT_TIMEOUT_MS8000L
    +
    • diff --git a/docs/doc/reference/deprecated-list.html b/docs/doc/reference/deprecated-list.html index 6448b21265..c00ee01863 100644 --- a/docs/doc/reference/deprecated-list.html +++ b/docs/doc/reference/deprecated-list.html @@ -1000,12 +1000,60 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +com.google.android.exoplayer2.SimpleExoPlayer.addAudioListener​(AudioListener) + + + +com.google.android.exoplayer2.SimpleExoPlayer.addDeviceListener​(DeviceListener) + + + +com.google.android.exoplayer2.SimpleExoPlayer.addListener​(Player.EventListener) + + + +com.google.android.exoplayer2.SimpleExoPlayer.addMetadataOutput​(MetadataOutput) + + + +com.google.android.exoplayer2.SimpleExoPlayer.addTextOutput​(TextOutput) + + + +com.google.android.exoplayer2.SimpleExoPlayer.addVideoListener​(VideoListener) + + + com.google.android.exoplayer2.SimpleExoPlayer.prepare​(MediaSource) +com.google.android.exoplayer2.SimpleExoPlayer.removeAudioListener​(AudioListener) + + + +com.google.android.exoplayer2.SimpleExoPlayer.removeDeviceListener​(DeviceListener) + + + +com.google.android.exoplayer2.SimpleExoPlayer.removeListener​(Player.EventListener) + + + +com.google.android.exoplayer2.SimpleExoPlayer.removeMetadataOutput​(MetadataOutput) + + + +com.google.android.exoplayer2.SimpleExoPlayer.removeTextOutput​(TextOutput) + + + +com.google.android.exoplayer2.SimpleExoPlayer.removeVideoListener​(VideoListener) + + + com.google.android.exoplayer2.SimpleExoPlayer.retry() @@ -1025,103 +1073,107 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +com.google.android.exoplayer2.SimpleExoPlayer.stop​(boolean) + + + com.google.android.exoplayer2.source.ads.AdsMediaSource.getTag() - + com.google.android.exoplayer2.source.ClippingMediaSource.getTag() - + com.google.android.exoplayer2.source.dash.DashMediaSource.Factory.createMediaSource​(Uri) - + com.google.android.exoplayer2.source.dash.DashMediaSource.Factory.setLivePresentationDelayMs​(long, boolean) - + com.google.android.exoplayer2.source.dash.DashMediaSource.Factory.setStreamKeys​(List<StreamKey>) - + com.google.android.exoplayer2.source.dash.DashMediaSource.Factory.setTag​(Object) - + com.google.android.exoplayer2.source.dash.DashMediaSource.getTag() - + com.google.android.exoplayer2.source.DefaultMediaSourceFactory.setStreamKeys​(List<StreamKey>) - + com.google.android.exoplayer2.source.hls.HlsMediaSource.Factory.createMediaSource​(Uri) - + com.google.android.exoplayer2.source.hls.HlsMediaSource.Factory.setStreamKeys​(List<StreamKey>) - + com.google.android.exoplayer2.source.hls.HlsMediaSource.Factory.setTag​(Object) - + com.google.android.exoplayer2.source.hls.HlsMediaSource.getTag() - + com.google.android.exoplayer2.source.LoopingMediaSource.getTag() - + com.google.android.exoplayer2.source.MaskingMediaSource.getTag() - + com.google.android.exoplayer2.source.MediaSource.getTag() - + com.google.android.exoplayer2.source.MediaSourceFactory.createMediaSource​(Uri) - + com.google.android.exoplayer2.source.MediaSourceFactory.setDrmHttpDataSourceFactory​(HttpDataSource.Factory)
      Use MediaSourceFactory.setDrmSessionManagerProvider(DrmSessionManagerProvider) and pass an @@ -1129,14 +1181,14 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); HttpDataSource.Factory.
      - + com.google.android.exoplayer2.source.MediaSourceFactory.setDrmSessionManager​(DrmSessionManager)
      Use MediaSourceFactory.setDrmSessionManagerProvider(DrmSessionManagerProvider) and pass an implementation that always returns the same instance.
      - + com.google.android.exoplayer2.source.MediaSourceFactory.setDrmUserAgent​(String)
      Use MediaSourceFactory.setDrmSessionManagerProvider(DrmSessionManagerProvider) and pass an @@ -1144,31 +1196,31 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); userAgent.
      - + com.google.android.exoplayer2.source.MediaSourceFactory.setStreamKeys​(List<StreamKey>) - + com.google.android.exoplayer2.source.MergingMediaSource.getTag() - + com.google.android.exoplayer2.source.ProgressiveMediaSource.Factory.createMediaSource​(Uri) - + com.google.android.exoplayer2.source.ProgressiveMediaSource.Factory.setCustomCacheKey​(String) - + com.google.android.exoplayer2.source.ProgressiveMediaSource.Factory.setExtractorsFactory​(ExtractorsFactory) - + com.google.android.exoplayer2.source.ProgressiveMediaSource.Factory.setTag​(Object) - + com.google.android.exoplayer2.source.ProgressiveMediaSource.getTag() - + com.google.android.exoplayer2.source.rtsp.RtspMediaSource.Factory.setDrmHttpDataSourceFactory​(HttpDataSource.Factory)
      RtspMediaSource does not support DRM.
      - + com.google.android.exoplayer2.source.rtsp.RtspMediaSource.Factory.setDrmSessionManager​(DrmSessionManager)
      RtspMediaSource does not support DRM.
      - + com.google.android.exoplayer2.source.rtsp.RtspMediaSource.Factory.setDrmUserAgent​(String)
      RtspMediaSource does not support DRM.
      - + com.google.android.exoplayer2.source.SilenceMediaSource.getTag() - + com.google.android.exoplayer2.source.SingleSampleMediaSource.Factory.createMediaSource​(Uri, Format, long) - + com.google.android.exoplayer2.source.SingleSampleMediaSource.getTag() - + com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource.Factory.createMediaSource​(Uri) - + com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource.Factory.setStreamKeys​(List<StreamKey>) - + com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource.Factory.setTag​(Object) - + com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource.getTag() - + com.google.android.exoplayer2.testutil.FakeMediaSource.getTag() - + com.google.android.exoplayer2.testutil.StubExoPlayer.prepare() - + com.google.android.exoplayer2.testutil.StubExoPlayer.retry() - + com.google.android.exoplayer2.Timeline.getWindow​(int, Timeline.Window, boolean)
      Use Timeline.getWindow(int, Window) instead. Tags will always be set.
      - + com.google.android.exoplayer2.ui.PlayerControlView.setFastForwardIncrementMs​(int) - + com.google.android.exoplayer2.ui.PlayerControlView.setPlaybackPreparer​(PlaybackPreparer)
      Use PlayerControlView.setControlDispatcher(ControlDispatcher) instead. The view calls ControlDispatcher.dispatchPrepare(Player) instead of PlaybackPreparer.preparePlayback(). The DefaultControlDispatcher that the view @@ -1286,25 +1338,25 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); you can provide a custom implementation of ControlDispatcher.dispatchPrepare(Player).
      - + com.google.android.exoplayer2.ui.PlayerControlView.setRewindIncrementMs​(int) - + com.google.android.exoplayer2.ui.PlayerNotificationManager.createWithNotificationChannel​(Context, String, int, int, PlayerNotificationManager.MediaDescriptionAdapter) - + com.google.android.exoplayer2.ui.PlayerNotificationManager.setFastForwardIncrementMs​(long) - + com.google.android.exoplayer2.ui.PlayerNotificationManager.setPlaybackPreparer​(PlaybackPreparer)
      Use PlayerNotificationManager.setControlDispatcher(ControlDispatcher) instead. The manager calls @@ -1313,31 +1365,31 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); behaviour, you can provide a custom implementation of ControlDispatcher.dispatchPrepare(Player) and pass it to PlayerNotificationManager.setControlDispatcher(ControlDispatcher).
      - + com.google.android.exoplayer2.ui.PlayerNotificationManager.setRewindIncrementMs​(long) - + com.google.android.exoplayer2.ui.PlayerNotificationManager.setUseNavigationActions​(boolean) - + com.google.android.exoplayer2.ui.PlayerNotificationManager.setUseNavigationActionsInCompactView​(boolean) - + com.google.android.exoplayer2.ui.PlayerView.setFastForwardIncrementMs​(int) - + com.google.android.exoplayer2.ui.PlayerView.setPlaybackPreparer​(PlaybackPreparer)
      Use PlayerView.setControlDispatcher(ControlDispatcher) instead. The view calls ControlDispatcher.dispatchPrepare(Player) instead of PlaybackPreparer.preparePlayback(). The DefaultControlDispatcher that the view @@ -1345,13 +1397,13 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); you can provide a custom implementation of ControlDispatcher.dispatchPrepare(Player).
      - + com.google.android.exoplayer2.ui.PlayerView.setRewindIncrementMs​(int) - + com.google.android.exoplayer2.ui.StyledPlayerControlView.setPlaybackPreparer​(PlaybackPreparer)
      Use StyledPlayerControlView.setControlDispatcher(ControlDispatcher) instead. The view calls ControlDispatcher.dispatchPrepare(Player) instead of PlaybackPreparer.preparePlayback(). The DefaultControlDispatcher that the view @@ -1359,7 +1411,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); you can provide a custom implementation of ControlDispatcher.dispatchPrepare(Player).
      - + com.google.android.exoplayer2.ui.StyledPlayerView.setPlaybackPreparer​(PlaybackPreparer)
      Use StyledPlayerView.setControlDispatcher(ControlDispatcher) instead. The view calls ControlDispatcher.dispatchPrepare(Player) instead of PlaybackPreparer.preparePlayback(). The DefaultControlDispatcher that the view @@ -1367,56 +1419,56 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); you can provide a custom implementation of ControlDispatcher.dispatchPrepare(Player).
      - + com.google.android.exoplayer2.upstream.DefaultHttpDataSource.Factory.getDefaultRequestProperties() - + com.google.android.exoplayer2.upstream.DefaultHttpDataSource.setContentTypePredicate​(Predicate<String>) - + com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory.getDefaultRequestProperties() - + com.google.android.exoplayer2.upstream.HttpDataSource.Factory.getDefaultRequestProperties() - + com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.getBlacklistDurationMsFor​(int, long, IOException, int) - + com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.getRetryDelayMsFor​(int, long, IOException, int) - + com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView.getVideoDecoderOutputBufferRenderer()
      This class implements VideoDecoderOutputBufferRenderer directly.
      - + com.google.android.exoplayer2.video.VideoListener.onVideoSizeChanged​(int, int, int, float) - + com.google.android.exoplayer2.video.VideoRendererEventListener.onVideoInputFormatChanged​(Format) @@ -1531,61 +1583,67 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); +com.google.android.exoplayer2.testutil.FakeExoMediaDrm() + + + + + com.google.android.exoplayer2.text.Cue​(CharSequence) - + com.google.android.exoplayer2.trackselection.DefaultTrackSelector() - + com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder()
      Context constraints will not be set using this constructor. Use ParametersBuilder(Context) instead.
      - + com.google.android.exoplayer2.trackselection.TrackSelectionParameters.Builder()
      Context constraints will not be set when using this constructor. Use Builder(Context) instead.
      - + com.google.android.exoplayer2.ui.PlayerNotificationManager​(Context, String, int, PlayerNotificationManager.MediaDescriptionAdapter) - + com.google.android.exoplayer2.upstream.cache.SimpleCache​(File, CacheEvictor)
      Use a constructor that takes a DatabaseProvider for improved performance.
      - + com.google.android.exoplayer2.upstream.DataSpec​(Uri, int) - + com.google.android.exoplayer2.upstream.DefaultBandwidthMeter() - + com.google.android.exoplayer2.upstream.DefaultHttpDataSource() - + com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException​(int, Map<String, List<String>>, DataSpec) diff --git a/docs/doc/reference/index-all.html b/docs/doc/reference/index-all.html index 81a1bea343..ee16510d07 100644 --- a/docs/doc/reference/index-all.html +++ b/docs/doc/reference/index-all.html @@ -454,7 +454,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Action.WaitForIsLoading - Class in com.google.android.exoplayer2.testutil
      -
      Waits for a specified loading state, returning either immediately or after a call to Player.EventListener.onIsLoadingChanged(boolean).
      +
      Waits for a specified loading state, returning either immediately or after a call to Player.Listener.onIsLoadingChanged(boolean).
      Action.WaitForMessage - Class in com.google.android.exoplayer2.testutil
      @@ -466,21 +466,21 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Action.WaitForPlaybackState - Class in com.google.android.exoplayer2.testutil
      -
      Waits for a specified playback state, returning either immediately or after a call to Player.EventListener.onPlaybackStateChanged(int).
      +
      Waits for a specified playback state, returning either immediately or after a call to Player.Listener.onPlaybackStateChanged(int).
      Action.WaitForPlayWhenReady - Class in com.google.android.exoplayer2.testutil
      Waits for a specified playWhenReady value, returning either immediately or after a call to - Player.EventListener.onPlayWhenReadyChanged(boolean, int).
      + Player.Listener.onPlayWhenReadyChanged(boolean, int).
      Action.WaitForPositionDiscontinuity - Class in com.google.android.exoplayer2.testutil
      -
      Action.WaitForTimelineChanged - Class in com.google.android.exoplayer2.testutil
      - +
      ActionFileUpgradeUtil - Class in com.google.android.exoplayer2.offline
      @@ -665,7 +665,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addAudioListener(AudioListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      addAudioOffloadListener(ExoPlayer.AudioOffloadListener) - Method in interface com.google.android.exoplayer2.ExoPlayer
      Adds a listener to receive audio offload events.
      @@ -681,7 +683,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addDeviceListener(DeviceListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      addDownload(DownloadRequest) - Method in class com.google.android.exoplayer2.offline.DownloadManager
      Adds a download defined by the given request.
      @@ -771,7 +775,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addListener(Player.EventListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      addListener(Player.EventListener) - Method in class com.google.android.exoplayer2.testutil.StubExoPlayer
       
      addListener(Player.Listener) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
      @@ -908,7 +914,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addMetadataOutput(MetadataOutput) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      addOrReplaceSpan(Spannable, Object, int, int, int) - Static method in class com.google.android.exoplayer2.text.span.SpanUtil
      Adds span to spannable between start and end, removing any @@ -934,7 +942,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addTextOutput(TextOutput) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      addTime(String, long) - Method in class com.google.android.exoplayer2.testutil.Dumper
       
      addTrackSelection(int, DefaultTrackSelector.Parameters) - Method in class com.google.android.exoplayer2.offline.DownloadHelper
      @@ -979,7 +989,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      addVideoListener(VideoListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      addVideoSurfaceListener(SphericalGLSurfaceView.VideoSurfaceListener) - Method in class com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView
      @@ -1634,12 +1646,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      assertMediaItemsTransitionedSame(MediaItem...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the media items reported by Player.EventListener.onMediaItemTransition(MediaItem, int) are the same as the provided media +
      Asserts that the media items reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided media items.
      assertMediaItemsTransitionReasonsEqual(Integer...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the media item transition reasons reported by Player.EventListener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
      +
      Asserts that the media item transition reasons reported by Player.Listener.onMediaItemTransition(MediaItem, int) are the same as the provided reasons.
      assertMediaPeriodCreated(MediaSource.MediaPeriodId) - Method in class com.google.android.exoplayer2.testutil.FakeMediaSource
      @@ -1652,7 +1664,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      assertNoPositionDiscontinuities() - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      assertNoTimelineChange() - Method in class com.google.android.exoplayer2.testutil.MediaSourceTestRunner
      @@ -1697,7 +1709,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      assertPlaybackStatesEqual(Integer...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the playback states reported by Player.EventListener.onPlaybackStateChanged(int) are equal to the provided playback states.
      +
      Asserts that the playback states reported by Player.Listener.onPlaybackStateChanged(int) are equal to the provided playback states.
      assertPlayedPeriodIndices(Integer...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      @@ -1705,7 +1717,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      assertPositionDiscontinuityReasonsEqual(Integer...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the discontinuity reasons reported by Player.EventListener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are +
      Asserts that the discontinuity reasons reported by Player.Listener.onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) are equal to the provided values.
      assertPrepareAndReleaseAllPeriods() - Method in class com.google.android.exoplayer2.testutil.MediaSourceTestRunner
      @@ -1760,12 +1772,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      assertTimelineChangeReasonsEqual(Integer...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the timeline change reasons reported by Player.EventListener.onTimelineChanged(Timeline, int) are equal to the provided timeline change +
      Asserts that the timeline change reasons reported by Player.Listener.onTimelineChanged(Timeline, int) are equal to the provided timeline change reasons.
      assertTimelinesSame(Timeline...) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the timelines reported by Player.EventListener.onTimelineChanged(Timeline, int) +
      Asserts that the timelines reported by Player.Listener.onTimelineChanged(Timeline, int) are the same to the provided timelines.
      assertTotalBufferCount(String, DecoderCounters, int, int) - Static method in class com.google.android.exoplayer2.testutil.DecoderCountersUtil
      @@ -1776,7 +1788,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      assertTrackGroupsEqual(TrackGroupArray) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
      -
      Asserts that the last track group array reported by Player.EventListener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided +
      Asserts that the last track group array reported by Player.Listener.onTracksChanged(TrackGroupArray, TrackSelectionArray) is equal to the provided track group array.
      assertVideoFrameProcessingOffsetSampleCount(String, DecoderCounters, int, int) - Static method in class com.google.android.exoplayer2.testutil.DecoderCountersUtil
      @@ -2580,6 +2592,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      build() - Method in class com.google.android.exoplayer2.testutil.ExtractorAsserts.AssertionConfig.Builder
       
      +
      build() - Method in class com.google.android.exoplayer2.testutil.FakeExoMediaDrm.Builder
      +
      +
      Returns a FakeExoMediaDrm instance with an initial reference count of 1.
      +
      build() - Method in class com.google.android.exoplayer2.testutil.FakeExtractorInput.Builder
       
      build() - Method in class com.google.android.exoplayer2.testutil.TestExoPlayerBuilder
      @@ -2726,6 +2742,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      Builder() - Constructor for class com.google.android.exoplayer2.testutil.ExtractorAsserts.AssertionConfig.Builder
       
      +
      Builder() - Constructor for class com.google.android.exoplayer2.testutil.FakeExoMediaDrm.Builder
      +
      +
      Constructs an instance.
      +
      Builder() - Constructor for class com.google.android.exoplayer2.testutil.FakeExtractorInput.Builder
       
      Builder() - Constructor for class com.google.android.exoplayer2.testutil.WebServerDispatcher.Resource.Builder
      @@ -7570,6 +7590,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      The default timeout applied when calling RobolectricUtil.runMainLooperUntil(Supplier).
      +
      DEFAULT_TIMEOUT_MS - Static variable in class com.google.android.exoplayer2.source.rtsp.RtspMediaSource
      +
      + +
      DEFAULT_TIMESTAMP_SEARCH_BYTES - Static variable in class com.google.android.exoplayer2.extractor.ts.TsExtractor
       
      DEFAULT_TOUCH_TARGET_HEIGHT_DP - Static variable in class com.google.android.exoplayer2.ui.DefaultTimeBar
      @@ -9330,6 +9354,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      e(String, String, Throwable) - Static method in class com.google.android.exoplayer2.util.Log
       
      +
      E_AC_3_CODEC_STRING - Static variable in class com.google.android.exoplayer2.audio.Ac3Util
      +
      +
      A non-standard codec string for E-AC-3.
      +
      E_AC3_MAX_RATE_BYTES_PER_SECOND - Static variable in class com.google.android.exoplayer2.audio.Ac3Util
      Maximum rate for an E-AC-3 audio stream, in bytes per second.
      @@ -10727,6 +10755,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Factory for arrays of Extractor instances.
      +
      ExtractorUtil - Class in com.google.android.exoplayer2.extractor
      +
      +
      Extractor related utility methods.
      +
      extractSeekMap(Extractor, FakeExtractorOutput, DataSource, Uri) - Static method in class com.google.android.exoplayer2.testutil.TestUtil
      Reads from the given input using the given Extractor, until it can produce the SeekMap and all of the track formats have been identified, or until the extractor encounters @@ -11091,13 +11123,19 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      FakeExoMediaDrm() - Constructor for class com.google.android.exoplayer2.testutil.FakeExoMediaDrm
      -
      Constructs an instance that returns random and unique sessionIds for subsequent calls - to FakeExoMediaDrm.openSession() with no limit on the number of concurrent open sessions.
      +
      Deprecated. + +
      FakeExoMediaDrm(int) - Constructor for class com.google.android.exoplayer2.testutil.FakeExoMediaDrm
      -
      Constructs an instance that returns random and unique sessionIds for subsequent calls - to FakeExoMediaDrm.openSession() with a limit on the number of concurrent open sessions.
      +
      Deprecated. + +
      +
      +
      FakeExoMediaDrm.Builder - Class in com.google.android.exoplayer2.testutil
      +
      +
      Builder for FakeExoMediaDrm instances.
      FakeExoMediaDrm.LicenseServer - Class in com.google.android.exoplayer2.testutil
      @@ -17335,6 +17373,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the ad group at index adGroupIndex has been played.
      +
      hasPositiveStartOffset - Variable in class com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
      +
      +
      Whether the HlsMediaPlaylist.startOffsetUs was explicitly defined by #EXT-X-START as a positive value + or zero.
      +
      hasPrevious() - Method in class com.google.android.exoplayer2.BasePlayer
       
      hasPrevious() - Method in interface com.google.android.exoplayer2.Player
      @@ -19072,6 +19115,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Returns whether the upstream source is ready.
      +
      isStartOfTsPacket(byte[], int, int, int) - Static method in class com.google.android.exoplayer2.extractor.ts.TsUtil
      +
      +
      Returns whether a TS packet starts at searchPosition according to the MPEG-TS + synchronization recommendations.
      +
      isStartTag(XmlPullParser) - Static method in class com.google.android.exoplayer2.util.XmlPullParserUtil
      Returns whether the current event is a start tag.
      @@ -21894,6 +21942,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the audio attributes change.
      +
      onAudioAttributesChanged(AudioAttributes) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onAudioCapabilitiesChanged(AudioCapabilities) - Method in interface com.google.android.exoplayer2.audio.AudioCapabilitiesReceiver.Listener
      Called when the audio capabilities change.
      @@ -22009,6 +22059,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the audio session ID changes.
      +
      onAudioSessionIdChanged(int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onAudioSessionIdChanged(AnalyticsListener.EventTime, int) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when the audio session ID changes.
      @@ -22047,6 +22099,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Called when the value returned from Player.isCommandAvailable(int) changes for at least one Player.Command.
      +
      onAvailableCommandsChanged(Player.Commands) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onBandwidthEstimate(AnalyticsListener.EventTime, int, long, long) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when the bandwidth estimate for the current data source has been updated.
      @@ -22326,11 +22380,15 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the device information changes.
      +
      onDeviceInfoChanged(DeviceInfo) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onDeviceVolumeChanged(int, boolean) - Method in interface com.google.android.exoplayer2.device.DeviceListener
      Deprecated.
      Called when the device volume or mute state changes.
      +
      onDeviceVolumeChanged(int, boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onDisabled() - Method in class com.google.android.exoplayer2.audio.DecoderAudioRenderer
       
      onDisabled() - Method in class com.google.android.exoplayer2.audio.MediaCodecAudioRenderer
      @@ -22573,6 +22631,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when one or more player states changed.
      +
      onEvents(Player, Player.Events) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onExperimentalOffloadSchedulingEnabledChanged(boolean) - Method in interface com.google.android.exoplayer2.ExoPlayer.AudioOffloadListener
      Called when the player has started or stopped offload scheduling using ExoPlayer.experimentalSetOffloadSchedulingEnabled(boolean).
      @@ -22669,6 +22729,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the player starts or stops loading the source.
      +
      onIsLoadingChanged(boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onIsLoadingChanged(AnalyticsListener.EventTime, boolean) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when the player starts or stops loading data from a source.
      @@ -22682,6 +22744,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the value of Player.isPlaying() changes.
      +
      onIsPlayingChanged(boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onIsPlayingChanged(AnalyticsListener.EventTime, boolean) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when the player starts or stops playing.
      @@ -22823,6 +22887,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Called when playback transitions to a media item or starts repeating a media item according to the current repeat mode.
      +
      onMediaItemTransition(MediaItem, int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onMediaItemTransition(MediaItem, int) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      onMediaMetadataChanged(AnalyticsListener.EventTime, MediaMetadata) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      @@ -22836,6 +22902,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the combined MediaMetadata changes.
      +
      onMediaMetadataChanged(MediaMetadata) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onMessageArrived() - Method in interface com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget.Callback
      Notifies about the arrival of the message.
      @@ -22915,6 +22983,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the current playback parameters change.
      +
      onPlaybackParametersChanged(PlaybackParameters) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onPlaybackSpeed(float) - Method in class com.google.android.exoplayer2.testutil.FakeTrackSelection
       
      onPlaybackSpeed(float) - Method in class com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
      @@ -22936,6 +23006,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the value returned from Player.getPlaybackState() changes.
      +
      onPlaybackStateChanged(int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onPlaybackStateChanged(int) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      onPlaybackStateChanged(int) - Method in class com.google.android.exoplayer2.util.DebugTextViewHelper
      @@ -22957,6 +23029,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the value returned from Player.getPlaybackSuppressionReason() changes.
      +
      onPlaybackSuppressionReasonChanged(int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onPlaybackSuppressionReasonChanged(AnalyticsListener.EventTime, int) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when playback suppression reason changed.
      @@ -22976,6 +23050,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when an error occurs.
      +
      onPlayerError(ExoPlaybackException) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onPlayerError(ExoPlaybackException) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      onPlayerErrorInternal(ExoPlaybackException) - Method in class com.google.android.exoplayer2.testutil.ExoHostedTest
      @@ -23023,6 +23099,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the value returned from Player.getPlayWhenReady() changes.
      +
      onPlayWhenReadyChanged(boolean, int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onPlayWhenReadyChanged(boolean, int) - Method in class com.google.android.exoplayer2.util.DebugTextViewHelper
       
      onPlayWhenReadyChanged(AnalyticsListener.EventTime, boolean, int) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      @@ -23079,6 +23157,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when a position discontinuity occurs.
      +
      onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) - Method in class com.google.android.exoplayer2.util.DebugTextViewHelper
      @@ -23258,6 +23338,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      onRemoveQueueItem(Player, MediaDescriptionCompat) - Method in class com.google.android.exoplayer2.ext.mediasession.TimelineQueueEditor
       
      +
      onRenderedFirstFrame() - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onRenderedFirstFrame() - Method in interface com.google.android.exoplayer2.video.VideoListener
      Deprecated.
      @@ -23291,6 +23373,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the value of Player.getRepeatMode() changes.
      +
      onRepeatModeChanged(int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onRepeatModeChanged(AnalyticsListener.EventTime, int) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when the repeat mode changed.
      @@ -23446,6 +23530,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the value of Player.getShuffleModeEnabled() changes.
      +
      onShuffleModeEnabledChanged(boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onSkipBackward(MediaSession, MediaSession.ControllerInfo) - Method in interface com.google.android.exoplayer2.ext.media2.SessionCallbackBuilder.SkipCallback
      Called when the specified controller has sent skip backward.
      @@ -23469,6 +23555,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Called when skipping silences is enabled or disabled.
      +
      onSkipSilenceEnabledChanged(boolean) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onSkipSilenceEnabledChanged(AnalyticsListener.EventTime, boolean) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when skipping silences is enabled or disabled in the audio stream.
      @@ -23588,6 +23676,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the static metadata changes.
      +
      onStaticMetadataChanged(List<Metadata>) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onStop() - Method in class com.google.android.exoplayer2.testutil.HostActivity
       
      onStopJob(JobParameters) - Method in class com.google.android.exoplayer2.scheduler.PlatformScheduler.PlatformSchedulerService
      @@ -23647,6 +23737,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Called each time there's a change in the size of the surface onto which the video is being rendered.
      +
      onSurfaceSizeChanged(int, int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onSurfaceSizeChanged(int, int) - Method in interface com.google.android.exoplayer2.video.VideoListener
      Deprecated.
      @@ -23691,6 +23783,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the timeline has been refreshed.
      +
      onTimelineChanged(Timeline, int) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onTimelineChanged(Timeline, int) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      onTimelineChanged(Timeline, Object, int) - Method in interface com.google.android.exoplayer2.Player.EventListener
      @@ -23728,6 +23822,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the available or selected tracks change.
      +
      onTracksChanged(TrackGroupArray, TrackSelectionArray) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onTracksChanged(TrackGroupArray, TrackSelectionArray) - Method in class com.google.android.exoplayer2.testutil.ExoPlayerTestRunner
       
      onTrackSelectionChanged(boolean, List<DefaultTrackSelector.SelectionOverride>) - Method in interface com.google.android.exoplayer2.ui.TrackSelectionView.TrackSelectionListener
      @@ -23951,6 +24047,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      onVideoSizeChanged(VideoSize) - Method in class com.google.android.exoplayer2.analytics.AnalyticsCollector
       
      +
      onVideoSizeChanged(VideoSize) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onVideoSizeChanged(VideoSize) - Method in interface com.google.android.exoplayer2.video.VideoListener
      Deprecated.
      @@ -23986,6 +24084,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Deprecated.
      Called when the volume changes.
      +
      onVolumeChanged(float) - Method in interface com.google.android.exoplayer2.Player.Listener
      +
       
      onVolumeChanged(AnalyticsListener.EventTime, float) - Method in interface com.google.android.exoplayer2.analytics.AnalyticsListener
      Called when the volume changes.
      @@ -24721,6 +24821,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      peekFully(byte[], int, int, boolean) - Method in class com.google.android.exoplayer2.testutil.FakeExtractorInput
       
      +
      peekFullyQuietly(ExtractorInput, byte[], int, int, boolean) - Static method in class com.google.android.exoplayer2.extractor.ExtractorUtil
      +
      +
      Peeks data from input, respecting allowEndOfInput.
      +
      peekId3Data(ExtractorInput, Id3Decoder.FramePredicate) - Method in class com.google.android.exoplayer2.extractor.Id3Peeker
      Peeks ID3 data from the input and parses the first ID3 tag.
      @@ -24734,6 +24838,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Peeks the source id of the next sample to be read, or the current upstream source id if the queue is empty or if the read position is at the end of the queue.
      +
      peekToLength(ExtractorInput, byte[], int, int) - Static method in class com.google.android.exoplayer2.extractor.ExtractorUtil
      +
      +
      Peeks length bytes from the input peek position, or all the bytes to the end of the + input if there was less than length bytes left.
      +
      peekUnsignedByte() - Method in class com.google.android.exoplayer2.util.ParsableByteArray
      Peeks at the next byte as an unsigned value.
      @@ -25120,7 +25229,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Player.EventFlags - Annotation Type in com.google.android.exoplayer2
      -
      Events that can be reported via Player.EventListener.onEvents(Player, Events).
      +
      Events that can be reported via Player.Listener.onEvents(Player, Events).
      Player.EventListener - Interface in com.google.android.exoplayer2
      @@ -26413,6 +26522,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      readFully(byte[], int, int, boolean) - Method in class com.google.android.exoplayer2.testutil.FakeExtractorInput
       
      +
      readFullyQuietly(ExtractorInput, byte[], int, int) - Static method in class com.google.android.exoplayer2.extractor.ExtractorUtil
      +
      +
      Equivalent to ExtractorInput.readFully(byte[], int, int) except that it returns + false instead of throwing an EOFException if the end of input is encountered without + having fully satisfied the read.
      +
      readId3Metadata(ExtractorInput, boolean) - Static method in class com.google.android.exoplayer2.extractor.FlacMetadataReader
      Reads ID3 Data.
      @@ -27141,7 +27256,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeAudioListener(AudioListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      removeAudioOffloadListener(ExoPlayer.AudioOffloadListener) - Method in interface com.google.android.exoplayer2.ExoPlayer
      Removes a listener of audio offload events.
      @@ -27161,7 +27278,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeDeviceListener(DeviceListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      removeDownload(String) - Method in class com.google.android.exoplayer2.offline.DefaultDownloadIndex
       
      removeDownload(String) - Method in class com.google.android.exoplayer2.offline.DownloadManager
      @@ -27223,7 +27342,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeListener(Player.EventListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      removeListener(Player.EventListener) - Method in class com.google.android.exoplayer2.testutil.StubExoPlayer
       
      removeListener(Player.Listener) - Method in class com.google.android.exoplayer2.ext.cast.CastPlayer
      @@ -27315,7 +27436,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeMetadataOutput(MetadataOutput) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      removePlaylistItem(int) - Method in class com.google.android.exoplayer2.ext.media2.SessionPlayerConnector
       
      removeQueryParameter(Uri, String) - Static method in class com.google.android.exoplayer2.util.UriUtil
      @@ -27345,7 +27468,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeTextOutput(TextOutput) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      removeVersion(SQLiteDatabase, int, String) - Static method in class com.google.android.exoplayer2.database.VersionTable
      Removes the version of a specified instance of a feature.
      @@ -27357,7 +27482,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      removeVideoListener(VideoListener) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      removeVideoSurfaceListener(SphericalGLSurfaceView.VideoSurfaceListener) - Method in class com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView
      @@ -27866,6 +27993,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Resets the clock's position.
      +
      resetProvisioning() - Method in class com.google.android.exoplayer2.testutil.FakeExoMediaDrm
      +
      +
      Resets the provisioning state of this instance, so it requires provisionsRequired (possibly zero) provision operations + before it's operational again.
      +
      resetSupplementalData(int) - Method in class com.google.android.exoplayer2.decoder.DecoderInputBuffer
      Clears DecoderInputBuffer.supplementalData and ensures that it's large enough to accommodate @@ -28356,7 +28488,7 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      runUntilPositionDiscontinuity(Player, int) - Static method in class com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
      -
      runUntilReceiveOffloadSchedulingEnabledNewState(ExoPlayer) - Static method in class com.google.android.exoplayer2.robolectric.TestPlayerRunHelper
      @@ -31033,6 +31165,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the maximum allowed audio channel count.
      +
      setMaxConcurrentSessions(int) - Method in class com.google.android.exoplayer2.testutil.FakeExoMediaDrm.Builder
      +
      +
      Sets the maximum number of concurrent sessions the FakeExoMediaDrm will support.
      +
      setMaxInputSize(int) - Method in class com.google.android.exoplayer2.Format.Builder
      @@ -31876,6 +32012,11 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets the proportional control factor used to adjust the playback speed.
      +
      setProvisionsRequired(int) - Method in class com.google.android.exoplayer2.testutil.FakeExoMediaDrm.Builder
      +
      +
      Sets how many successful provisioning round trips are needed for the FakeExoMediaDrm + to be provisioned.
      +
      setQueueEditor(MediaSessionConnector.QueueEditor) - Method in class com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
      Sets the MediaSessionConnector.QueueEditor to handle queue edits sent by the media controller.
      @@ -32633,6 +32774,10 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      Sets a Timeline to be used by a FakeMediaSource in the test runner.
      +
      setTimeoutMs(long) - Method in class com.google.android.exoplayer2.source.rtsp.RtspMediaSource.Factory
      +
      +
      Sets the timeout in milliseconds, the default value is RtspMediaSource.DEFAULT_TIMEOUT_MS.
      +
      setTimestamp(long) - Method in class com.google.android.exoplayer2.source.rtsp.RtpPacket.Builder
      @@ -33400,17 +33545,24 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height")); Object, MediaItem) instead.
      +
      SinglePeriodTimeline(long, long, long, long, long, long, long, boolean, boolean, boolean, Object, MediaItem, MediaItem.LiveConfiguration) - Constructor for class com.google.android.exoplayer2.source.SinglePeriodTimeline
      +
      +
      Creates a timeline with one period, and a window of known duration starting at a specified + position in the period.
      +
      SinglePeriodTimeline(long, long, long, long, long, long, long, boolean, boolean, boolean, Object, Object) - Constructor for class com.google.android.exoplayer2.source.SinglePeriodTimeline
      SinglePeriodTimeline(long, long, long, long, long, long, long, boolean, boolean, Object, MediaItem, MediaItem.LiveConfiguration) - Constructor for class com.google.android.exoplayer2.source.SinglePeriodTimeline
      -
      Creates a timeline with one period, and a window of known duration starting at a specified - position in the period.
      +
      SingleSampleMediaChunk - Class in com.google.android.exoplayer2.source.chunk
      @@ -33569,6 +33721,12 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
       
      skipFully(int, boolean) - Method in class com.google.android.exoplayer2.testutil.FakeExtractorInput
       
      +
      skipFullyQuietly(ExtractorInput, int) - Static method in class com.google.android.exoplayer2.extractor.ExtractorUtil
      +
      +
      Equivalent to ExtractorInput.skipFully(int) except that it returns false + instead of throwing an EOFException if the end of input is encountered without having + fully satisfied the skip.
      +
      skipInputUntilPosition(ExtractorInput, long) - Method in class com.google.android.exoplayer2.extractor.BinarySearchSeeker
       
      skipOutputBuffer(MediaCodecAdapter, int, long) - Method in class com.google.android.exoplayer2.video.MediaCodecVideoRenderer
      @@ -34314,7 +34472,9 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));
      stop(boolean) - Method in class com.google.android.exoplayer2.SimpleExoPlayer
      -
       
      +
      +
      Deprecated.
      +
      stop(boolean) - Method in class com.google.android.exoplayer2.testutil.ActionSchedule.Builder
      Schedules a stop action.
      @@ -36582,6 +36742,8 @@ $('.navPadding').css('padding-top', $('.fixedNav').css("height"));

      V

      +
      VALID_PROVISION_RESPONSE - Static variable in class com.google.android.exoplayer2.testutil.FakeExoMediaDrm
      +
       
      validateWebvttHeaderLine(ParsableByteArray) - Static method in class com.google.android.exoplayer2.text.webvtt.WebvttParserUtil
      Reads and validates the first line of a WebVTT file.
      diff --git a/docs/doc/reference/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png b/docs/doc/reference/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100644 index ca17cb753b0a2ddbaacc849d284e0bf7af05428c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F1SA+{?>A)!QcOwS?k)_>#w|r1Kptm-M`SUO z_5fqIli7AahM1>|V~EA+ zRdP`(kYX@0Ff`RQFx53Q4>2&eGB&j`FxECOure@kD_zcvq9HdwB{QuOw+3?!`Tal* N44$rjF6*2UngCl5GAIB5 diff --git a/docs/doc/reference/jquery/images/ui-bg_flat_75_ffffff_40x100.png b/docs/doc/reference/jquery/images/ui-bg_flat_75_ffffff_40x100.png deleted file mode 100644 index e6a4176cf0b34674a44540e9ec03e5387ba31308..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F2qYNp$opRhQcOwS?k)_Bce{j_0C}7R9+AaB z+5?Q;PG;Ky8A6^ejv*T7lYj6t@hpC#;TbB#aBAWwna#KLs)4eqC9V-ADTyViR>?)F zK#IZ0z|d6Jz*N`JJjB4<%GlJ(z*yVBz{KdAd7?@ibn_3wdYa19?85p>gE@wv3kei>9nO2EggSm$MexL>hPgg&e IbxsLQ0Lv~h$N&HU diff --git a/docs/doc/reference/jquery/jquery-1.10.2.js b/docs/doc/reference/jquery/jquery-1.10.2.js deleted file mode 100644 index c5c648255c..0000000000 --- a/docs/doc/reference/jquery/jquery-1.10.2.js +++ /dev/null @@ -1,9789 +0,0 @@ -/*! - * jQuery JavaScript Library v1.10.2 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2013-07-03T13:48Z - */ -(function( window, undefined ) { - -// Can't do this because several apps including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -// Support: Firefox 18+ -//"use strict"; -var - // The deferred used on DOM ready - readyList, - - // A central reference to the root jQuery(document) - rootjQuery, - - // Support: IE<10 - // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` - core_strundefined = typeof undefined, - - // Use the correct document accordingly with window argument (sandbox) - location = window.location, - document = window.document, - docElem = document.documentElement, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // [[Class]] -> type pairs - class2type = {}, - - // List of deleted data cache ids, so we can reuse them - core_deletedIds = [], - - core_version = "1.10.2", - - // Save a reference to some core methods - core_concat = core_deletedIds.concat, - core_push = core_deletedIds.push, - core_slice = core_deletedIds.slice, - core_indexOf = core_deletedIds.indexOf, - core_toString = class2type.toString, - core_hasOwn = class2type.hasOwnProperty, - core_trim = core_version.trim, - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Used for matching numbers - core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, - - // Used for splitting on whitespace - core_rnotwhite = /\S+/g, - - // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, - rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }, - - // The ready event handler - completed = function( event ) { - - // readyState === "complete" is good enough for us to call the dom ready in oldIE - if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { - detach(); - jQuery.ready(); - } - }, - // Clean-up method for dom ready events - detach = function() { - if ( document.addEventListener ) { - document.removeEventListener( "DOMContentLoaded", completed, false ); - window.removeEventListener( "load", completed, false ); - - } else { - document.detachEvent( "onreadystatechange", completed ); - window.detachEvent( "onload", completed ); - } - }; - -jQuery.fn = jQuery.prototype = { - // The current version of jQuery being used - jquery: core_version, - - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // scripts is true for back-compat - jQuery.merge( this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return core_slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; - }, - - slice: function() { - return this.pushStack( core_slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: core_push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var src, copyIsArray, copy, name, options, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), - - noConflict: function( deep ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } - - if ( deep && window.jQuery === jQuery ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger("ready").off("ready"); - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - isWindow: function( obj ) { - /* jshint eqeqeq: false */ - return obj != null && obj == obj.window; - }, - - isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( obj ); - }, - - type: function( obj ) { - if ( obj == null ) { - return String( obj ); - } - return typeof obj === "object" || typeof obj === "function" ? - class2type[ core_toString.call(obj) ] || "object" : - typeof obj; - }, - - isPlainObject: function( obj ) { - var key; - - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - try { - // Not own constructor property must be Object - if ( obj.constructor && - !core_hasOwn.call(obj, "constructor") && - !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Support: IE<9 - // Handle iteration over inherited properties before own properties. - if ( jQuery.support.ownLast ) { - for ( key in obj ) { - return core_hasOwn.call( obj, key ); - } - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - for ( key in obj ) {} - - return key === undefined || core_hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw new Error( msg ); - }, - - // data: string of html - // context (optional): If specified, the fragment will be created in this context, defaults to document - // keepScripts (optional): If true, will include scripts passed in the html string - parseHTML: function( data, context, keepScripts ) { - if ( !data || typeof data !== "string" ) { - return null; - } - if ( typeof context === "boolean" ) { - keepScripts = context; - context = false; - } - context = context || document; - - var parsed = rsingleTag.exec( data ), - scripts = !keepScripts && []; - - // Single tag - if ( parsed ) { - return [ context.createElement( parsed[1] ) ]; - } - - parsed = jQuery.buildFragment( [ data ], context, scripts ); - if ( scripts ) { - jQuery( scripts ).remove(); - } - return jQuery.merge( [], parsed.childNodes ); - }, - - parseJSON: function( data ) { - // Attempt to parse using the native JSON parser first - if ( window.JSON && window.JSON.parse ) { - return window.JSON.parse( data ); - } - - if ( data === null ) { - return data; - } - - if ( typeof data === "string" ) { - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - if ( data ) { - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) - .replace( rvalidtokens, "]" ) - .replace( rvalidbraces, "")) ) { - - return ( new Function( "return " + data ) )(); - } - } - } - - jQuery.error( "Invalid JSON: " + data ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - var xml, tmp; - if ( !data || typeof data !== "string" ) { - return null; - } - try { - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - - // Evaluates a script in a global context - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function( data ) { - if ( data && jQuery.trim( data ) ) { - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - ( window.execScript || function( data ) { - window[ "eval" ].call( window, data ); - } )( data ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - // args is for internal usage only - each: function( obj, callback, args ) { - var value, - i = 0, - length = obj.length, - isArray = isArraylike( obj ); - - if ( args ) { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } - } - - return obj; - }, - - // Use native String.trim function wherever possible - trim: core_trim && !core_trim.call("\uFEFF\xA0") ? - function( text ) { - return text == null ? - "" : - core_trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArraylike( Object(arr) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - core_push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - var len; - - if ( arr ) { - if ( core_indexOf ) { - return core_indexOf.call( arr, elem, i ); - } - - len = arr.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - - for ( ; i < len; i++ ) { - // Skip accessing in sparse arrays - if ( i in arr && arr[ i ] === elem ) { - return i; - } - } - } - - return -1; - }, - - merge: function( first, second ) { - var l = second.length, - i = first.length, - j = 0; - - if ( typeof l === "number" ) { - for ( ; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var retVal, - ret = [], - i = 0, - length = elems.length; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, - i = 0, - length = elems.length, - isArray = isArraylike( elems ), - ret = []; - - // Go through the array, translating each of the items to their - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - } - - // Flatten any nested arrays - return core_concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var args, proxy, tmp; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = core_slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - // Multifunctional method to get and set values of a collection - // The value/s can optionally be executed if it's a function - access: function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - length = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < length; i++ ) { - fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[0], key ) : emptyGet; - }, - - now: function() { - return ( new Date() ).getTime(); - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations. - // Note: this method belongs to the css module but it's needed here for the support module. - // If support gets modularized, this method should be moved back to the css module. - swap: function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; - } -}); - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready ); - - // Standards-based browsers support DOMContentLoaded - } else if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed, false ); - - // If IE event model is used - } else { - // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", completed ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", completed ); - - // If IE and not a frame - // continually check to see if the document is ready - var top = false; - - try { - top = window.frameElement == null && document.documentElement; - } catch(e) {} - - if ( top && top.doScroll ) { - (function doScrollCheck() { - if ( !jQuery.isReady ) { - - try { - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll("left"); - } catch(e) { - return setTimeout( doScrollCheck, 50 ); - } - - // detach all dom ready events - detach(); - - // and execute any waiting functions - jQuery.ready(); - } - })(); - } - } - } - return readyList.promise( obj ); -}; - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -function isArraylike( obj ) { - var length = obj.length, - type = jQuery.type( obj ); - - if ( jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === "array" || type !== "function" && - ( length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj ); -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); -/*! - * Sizzle CSS Selector Engine v1.10.2 - * http://sizzlejs.com/ - * - * Copyright 2013 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2013-07-03 - */ -(function( window, undefined ) { - -var i, - support, - cachedruns, - Expr, - getText, - isXML, - compile, - outermostContext, - sortInput, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + -(new Date()), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - hasDuplicate = false, - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - return 0; - }, - - // General-purpose constants - strundefined = typeof undefined, - MAX_NEGATIVE = 1 << 31, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function( elem ) { - var i = 0, - len = this.length; - for ( ; i < len; i++ ) { - if ( this[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + - "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", - - // Prefer arguments quoted, - // then not containing pseudos/brackets, - // then attribute selectors/non-parenthetical expressions, - // then anything else - // These preferences are here to reduce the number of selectors - // needing tokenize in the PSEUDO preFilter - pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rsibling = new RegExp( whitespace + "*[+~]" ), - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - // BMP codepoint - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }; - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - - context = context || document; - results = results || []; - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { - return []; - } - - if ( documentIsHTML && !seed ) { - - // Shortcuts - if ( (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // QSA path - if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - nid = old = expando; - newContext = context; - newSelector = nodeType === 9 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + toSelector( groups[i] ); - } - newContext = rsibling.test( selector ) && context.parentNode || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key += " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return !!fn( div ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( div.parentNode ) { - div.parentNode.removeChild( div ); - } - // release memory in IE - div = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = attrs.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Detect xml - * @param {Element|Object} elem An element or a document - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var doc = node ? node.ownerDocument || node : preferredDoc, - parent = doc.defaultView; - - // If no document and documentElement is available, return - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Set our document - document = doc; - docElem = doc.documentElement; - - // Support tests - documentIsHTML = !isXML( doc ); - - // Support: IE>8 - // If iframe document is assigned to "document" variable and if iframe has been reloaded, - // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 - // IE6-8 do not support the defaultView property so parent will be undefined - if ( parent && parent.attachEvent && parent !== parent.top ) { - parent.attachEvent( "onbeforeunload", function() { - setDocument(); - }); - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) - support.attributes = assert(function( div ) { - div.className = "i"; - return !div.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( doc.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Check if getElementsByClassName can be trusted - support.getElementsByClassName = assert(function( div ) { - div.innerHTML = "
      "; - - // Support: Safari<4 - // Catch class over-caching - div.firstChild.className = "i"; - // Support: Opera<10 - // Catch gEBCN failure to find non-leading classes - return div.getElementsByClassName("i").length === 2; - }); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( div ) { - docElem.appendChild( div ).id = expando; - return !doc.getElementsByName || !doc.getElementsByName( expando ).length; - }); - - // ID find and filter - if ( support.getById ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && documentIsHTML ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { - return context.getElementsByTagName( tag ); - } - } : - function( tag, context ) { - var elem, - tmp = [], - i = 0, - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - - // Support: Opera 10-12/IE8 - // ^= $= *= and empty values - // Should not select anything - // Support: Windows 8 Native Apps - // The type attribute is restricted during .innerHTML assignment - var input = doc.createElement("input"); - input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "t", "" ); - - if ( div.querySelectorAll("[t^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = docElem.compareDocumentPosition ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); - - if ( compare ) { - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === doc || contains(preferredDoc, a) ) { - return -1; - } - if ( b === doc || contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } - - // Not directly comparable, sort on existence of method - return a.compareDocumentPosition ? -1 : 1; - } : - function( a, b ) { - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Parentless nodes are either documents or disconnected - } else if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return doc; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, document, null, [elem] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val === undefined ? - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null : - val; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - for ( ; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (see #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[5] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] && match[4] !== undefined ) { - match[2] = match[4]; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), - // not comment, processing instructions, or others - // Thanks to Diego Perini for the nodeName shortcut - // Greater than "@" means alpha characters (specifically not starting with "#" or "?") - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( tokens = [] ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var data, cache, outerCache, - dirkey = dirruns + " " + doneName; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { - if ( (data = cache[1]) === true || data === cachedruns ) { - return data === true; - } - } else { - cache = outerCache[ dir ] = [ dirkey ]; - cache[1] = matcher( elem, context, xml ) || cachedruns; - if ( cache[1] === true ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - // A counter to specify which element is currently being matched - var matcherCachedRuns = 0, - bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, expandContext ) { - var elem, j, matcher, - setMatched = [], - matchedCount = 0, - i = "0", - unmatched = seed && [], - outermost = expandContext != null, - contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); - - if ( outermost ) { - outermostContext = context !== document && context; - cachedruns = matcherCachedRuns; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - for ( ; (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - cachedruns = ++matcherCachedRuns; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !group ) { - group = tokenize( selector ); - } - i = group.length; - while ( i-- ) { - cached = matcherFromTokens( group[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - } - return cached; -}; - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function select( selector, context, results, seed ) { - var i, tokens, token, type, find, - match = tokenize( selector ); - - if ( !seed ) { - // Try to minimize operations if there is only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - } - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && context.parentNode || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - } - - // Compile and execute a filtering function - // Provide `match` to avoid retokenization if we modified the selector above - compile( selector, match )( - seed, - context, - !documentIsHTML, - results, - rsibling.test( selector ) - ); - return results; -} - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome<14 -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = ""; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - elem[ name ] === true ? name.toLowerCase() : null; - } - }); -} - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})( window ); -// String to Object options format cache -var optionsCache = {}; - -// Convert String-formatted options into Object-formatted ones and store in cache -function createOptions( options ) { - var object = optionsCache[ options ] = {}; - jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - }); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions( options ) ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // First callback to fire (used internally by add and fireWith) - firingStart, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function( data ) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if ( list ) { - if ( stack ) { - if ( stack.length ) { - fire( stack.shift() ); - } - } else if ( memory ) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - // First, we save the current length - var start = list.length; - (function add( args ) { - jQuery.each( args, function( _, arg ) { - var type = jQuery.type( arg ); - if ( type === "function" ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && type !== "string" ) { - // Inspect recursively - add( arg ); - } - }); - })( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - jQuery.each( arguments, function( _, arg ) { - var index; - while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - // Handle firing indexes - if ( firing ) { - if ( index <= firingLength ) { - firingLength--; - } - if ( index <= firingIndex ) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function() { - list = []; - firingLength = 0; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( list && ( !fired || stack ) ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; -jQuery.extend({ - - Deferred: function( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred(function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var action = tuple[ 0 ], - fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function() { - deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = core_slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; - if( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, progressValues ) ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -}); -jQuery.support = (function( support ) { - - var all, a, input, select, fragment, opt, eventName, isSupported, i, - div = document.createElement("div"); - - // Setup - div.setAttribute( "className", "t" ); - div.innerHTML = "
      a"; - - // Finish early in limited (non-browser) environments - all = div.getElementsByTagName("*") || []; - a = div.getElementsByTagName("a")[ 0 ]; - if ( !a || !a.style || !all.length ) { - return support; - } - - // First batch of tests - select = document.createElement("select"); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName("input")[ 0 ]; - - a.style.cssText = "top:1px;float:left;opacity:.5"; - - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - support.getSetAttribute = div.className !== "t"; - - // IE strips leading whitespace when .innerHTML is used - support.leadingWhitespace = div.firstChild.nodeType === 3; - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - support.tbody = !div.getElementsByTagName("tbody").length; - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - support.htmlSerialize = !!div.getElementsByTagName("link").length; - - // Get the style information from getAttribute - // (IE uses .cssText instead) - support.style = /top/.test( a.getAttribute("style") ); - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - support.hrefNormalized = a.getAttribute("href") === "/a"; - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - support.opacity = /^0.5/.test( a.style.opacity ); - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - support.cssFloat = !!a.style.cssFloat; - - // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) - support.checkOn = !!input.value; - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - support.optSelected = opt.selected; - - // Tests for enctype support on a form (#6743) - support.enctype = !!document.createElement("form").enctype; - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>"; - - // Will be defined later - support.inlineBlockNeedsLayout = false; - support.shrinkWrapBlocks = false; - support.pixelPosition = false; - support.deleteExpando = true; - support.noCloneEvent = true; - support.reliableMarginRight = true; - support.boxSizingReliable = true; - - // Make sure checked status is properly cloned - input.checked = true; - support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Support: IE<9 - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - // Check if we can trust getAttribute("value") - input = document.createElement("input"); - input.setAttribute( "value", "" ); - support.input = input.getAttribute( "value" ) === ""; - - // Check if an input maintains its value after becoming a radio - input.value = "t"; - input.setAttribute( "type", "radio" ); - support.radioValue = input.value === "t"; - - // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute( "checked", "t" ); - input.setAttribute( "name", "t" ); - - fragment = document.createDocumentFragment(); - fragment.appendChild( input ); - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<9 - // Opera does not clone events (and typeof div.attachEvent === undefined). - // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() - if ( div.attachEvent ) { - div.attachEvent( "onclick", function() { - support.noCloneEvent = false; - }); - - div.cloneNode( true ).click(); - } - - // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) - for ( i in { submit: true, change: true, focusin: true }) { - div.setAttribute( eventName = "on" + i, "t" ); - - support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; - } - - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - // Support: IE<9 - // Iteration over object's inherited properties before its own. - for ( i in jQuery( support ) ) { - break; - } - support.ownLast = i !== "0"; - - // Run tests that need a body at doc ready - jQuery(function() { - var container, marginDiv, tds, - divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", - body = document.getElementsByTagName("body")[0]; - - if ( !body ) { - // Return for frameset docs that don't have a body - return; - } - - container = document.createElement("div"); - container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; - - body.appendChild( container ).appendChild( div ); - - // Support: IE8 - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - div.innerHTML = "
      t
      "; - tds = div.getElementsByTagName("td"); - tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; - isSupported = ( tds[ 0 ].offsetHeight === 0 ); - - tds[ 0 ].style.display = ""; - tds[ 1 ].style.display = "none"; - - // Support: IE8 - // Check if empty table cells still have offsetWidth/Height - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - - // Check box-sizing and margin behavior. - div.innerHTML = ""; - div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; - - // Workaround failing boxSizing test due to offsetWidth returning wrong value - // with some non-1 values of body zoom, ticket #13543 - jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() { - support.boxSizing = div.offsetWidth === 4; - }); - - // Use window.getComputedStyle because jsdom on node.js will break without it. - if ( window.getComputedStyle ) { - support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; - support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. (#3333) - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = div.appendChild( document.createElement("div") ); - marginDiv.style.cssText = div.style.cssText = divReset; - marginDiv.style.marginRight = marginDiv.style.width = "0"; - div.style.width = "1px"; - - support.reliableMarginRight = - !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); - } - - if ( typeof div.style.zoom !== core_strundefined ) { - // Support: IE<8 - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - div.innerHTML = ""; - div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); - - // Support: IE6 - // Check if elements with layout shrink-wrap their children - div.style.display = "block"; - div.innerHTML = "
      "; - div.firstChild.style.width = "5px"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); - - if ( support.inlineBlockNeedsLayout ) { - // Prevent IE 6 from affecting layout for positioned elements #11048 - // Prevent IE from shrinking the body in IE 7 mode #12869 - // Support: IE<8 - body.style.zoom = 1; - } - } - - body.removeChild( container ); - - // Null elements to avoid leaks in IE - container = div = tds = marginDiv = null; - }); - - // Null elements to avoid leaks in IE - all = select = fragment = opt = a = input = null; - - return support; -})({}); - -var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, - rmultiDash = /([A-Z])/g; - -function internalData( elem, name, data, pvt /* Internal Use Only */ ){ - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var ret, thisCache, - internalKey = jQuery.expando, - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - // Avoid exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( typeof name === "string" ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; -} - -function internalRemoveData( elem, name, pvt ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } else { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - i = name.length; - while ( i-- ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - /* jshint eqeqeq: false */ - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - /* jshint eqeqeq: true */ - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } -} - -jQuery.extend({ - cache: {}, - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "applet": true, - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data ) { - return internalData( elem, name, data ); - }, - - removeData: function( elem, name ) { - return internalRemoveData( elem, name ); - }, - - // For internal use only. - _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); - }, - - _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - // Do not set data on non-element because it will not be cleared (#8335). - if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { - return false; - } - - var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; - - // nodes accept data unless otherwise specified; rejection can be conditional - return !noData || noData !== true && elem.getAttribute("classid") === noData; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var attrs, name, - data = null, - i = 0, - elem = this[0]; - - // Special expections of .data basically thwart jQuery.access, - // so implement the relevant behavior ourselves - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = jQuery.data( elem ); - - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - attrs = elem.attributes; - for ( ; i < attrs.length; i++ ) { - name = attrs[i].name; - - if ( name.indexOf("data-") === 0 ) { - name = jQuery.camelCase( name.slice(5) ); - - dataAttr( elem, name, data[ name ] ); - } - } - jQuery._data( elem, "parsedAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - return arguments.length > 1 ? - - // Sets one value - this.each(function() { - jQuery.data( this, key, value ); - }) : - - // Gets one value - // Try to fetch any internally stored data first - elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} -jQuery.extend({ - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray(data) ) { - queue = jQuery._data( elem, type, jQuery.makeArray(data) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // not intended for public consumption - generates a queueHooks object, or returns the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return jQuery._data( elem, key ) || jQuery._data( elem, key, { - empty: jQuery.Callbacks("once memory").add(function() { - jQuery._removeData( elem, type + "queue" ); - jQuery._removeData( elem, key ); - }) - }); - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - // ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while( i-- ) { - tmp = jQuery._data( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -}); -var nodeHook, boolHook, - rclass = /[\t\r\n\f]/g, - rreturn = /\r/g, - rfocusable = /^(?:input|select|textarea|button|object)$/i, - rclickable = /^(?:a|area)$/i, - ruseDefault = /^(?:checked|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - getSetInput = jQuery.support.input; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each(function() { - jQuery.removeAttr( this, name ); - }); - }, - - prop: function( name, value ) { - return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - name = jQuery.propFix[ name ] || name; - return this.each(function() { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[ name ] = undefined; - delete this[ name ]; - } catch( e ) {} - }); - }, - - addClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = typeof value === "string" && value; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call( this, j, this.className ) ); - }); - } - - if ( proceed ) { - // The disjunction here is for better compressibility (see removeClass) - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - " " - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - elem.className = jQuery.trim( cur ); - - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = arguments.length === 0 || typeof value === "string" && value; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).removeClass( value.call( this, j, this.className ) ); - }); - } - if ( proceed ) { - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - "" - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - elem.className = value ? jQuery.trim( cur ) : ""; - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value; - - if ( typeof stateVal === "boolean" && type === "string" ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( jQuery.isFunction( value ) ) { - return this.each(function( i ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - classNames = value.match( core_rnotwhite ) || []; - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( type === core_strundefined || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // If the element has a class name or if we're passed "false", - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var ret, hooks, isFunction, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each(function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map(val, function ( value ) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - valHooks: { - option: { - get: function( elem ) { - // Use proper attribute retrieval(#6932, #12072) - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - elem.text; - } - }, - select: { - get: function( elem ) { - var value, option, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one" || index < 0, - values = one ? null : [], - max = one ? index + 1 : options.length, - i = index < 0 ? - max : - one ? index : 0; - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // oldIE doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - // Don't return options that are disabled or in a disabled optgroup - ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && - ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) { - optionSet = true; - } - } - - // force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attr: function( elem, name, value ) { - var hooks, ret, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === core_strundefined ) { - return jQuery.prop( elem, name, value ); - } - - // All attributes are lowercase - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - - } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, value + "" ); - return value; - } - - } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, value ) { - var name, propName, - i = 0, - attrNames = value && value.match( core_rnotwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( (name = attrNames[i++]) ) { - propName = jQuery.propFix[ name ] || name; - - // Boolean attributes get special treatment (#10870) - if ( jQuery.expr.match.bool.test( name ) ) { - // Set corresponding property to false - if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { - elem[ propName ] = false; - // Support: IE<9 - // Also clear defaultChecked/defaultSelected (if appropriate) - } else { - elem[ jQuery.camelCase( "default-" + name ) ] = - elem[ propName ] = false; - } - - // See #9699 for explanation of this approach (setting first, then removal) - } else { - jQuery.attr( elem, name, "" ); - } - - elem.removeAttribute( getSetAttribute ? name : propName ); - } - } - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to default in case type is set after value during creation - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - }, - - prop: function( elem, name, value ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? - ret : - ( elem[ name ] = value ); - - } else { - return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? - ret : - elem[ name ]; - } - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - return tabindex ? - parseInt( tabindex, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - -1; - } - } - } -}); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { - // IE<8 needs the *property* name - elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); - - // Use defaultChecked and defaultSelected for oldIE - } else { - elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; - } - - return name; - } -}; -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; - - jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ? - function( elem, name, isXML ) { - var fn = jQuery.expr.attrHandle[ name ], - ret = isXML ? - undefined : - /* jshint eqeqeq: false */ - (jQuery.expr.attrHandle[ name ] = undefined) != - getter( elem, name, isXML ) ? - - name.toLowerCase() : - null; - jQuery.expr.attrHandle[ name ] = fn; - return ret; - } : - function( elem, name, isXML ) { - return isXML ? - undefined : - elem[ jQuery.camelCase( "default-" + name ) ] ? - name.toLowerCase() : - null; - }; -}); - -// fix oldIE attroperties -if ( !getSetInput || !getSetAttribute ) { - jQuery.attrHooks.value = { - set: function( elem, value, name ) { - if ( jQuery.nodeName( elem, "input" ) ) { - // Does not return so that setAttribute is also used - elem.defaultValue = value; - } else { - // Use nodeHook if defined (#1954); otherwise setAttribute is fine - return nodeHook && nodeHook.set( elem, value, name ); - } - } - }; -} - -// IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !getSetAttribute ) { - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = { - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - elem.setAttributeNode( - (ret = elem.ownerDocument.createAttribute( name )) - ); - } - - ret.value = value += ""; - - // Break association with cloned elements by also using setAttribute (#9646) - return name === "value" || value === elem.getAttribute( name ) ? - value : - undefined; - } - }; - jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords = - // Some attributes are constructed with empty-string values when not defined - function( elem, name, isXML ) { - var ret; - return isXML ? - undefined : - (ret = elem.getAttributeNode( name )) && ret.value !== "" ? - ret.value : - null; - }; - jQuery.valHooks.button = { - get: function( elem, name ) { - var ret = elem.getAttributeNode( name ); - return ret && ret.specified ? - ret.value : - undefined; - }, - set: nodeHook.set - }; - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - set: function( elem, value, name ) { - nodeHook.set( elem, value === "" ? false : value, name ); - } - }; - - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) - // This is for removals - jQuery.each([ "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = { - set: function( elem, value ) { - if ( value === "" ) { - elem.setAttribute( name, "auto" ); - return value; - } - } - }; - }); -} - - -// Some attributes require a special call on IE -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !jQuery.support.hrefNormalized ) { - // href/src property should get the full normalized URL (#10299/#12915) - jQuery.each([ "href", "src" ], function( i, name ) { - jQuery.propHooks[ name ] = { - get: function( elem ) { - return elem.getAttribute( name, 4 ); - } - }; - }); -} - -if ( !jQuery.support.style ) { - jQuery.attrHooks.style = { - get: function( elem ) { - // Return undefined in the case of empty string - // Note: IE uppercases css property names, but if we were to .toLowerCase() - // .cssText, that would destroy case senstitivity in URL's, like in "background" - return elem.style.cssText || undefined; - }, - set: function( elem, value ) { - return ( elem.style.cssText = value + "" ); - } - }; -} - -// Safari mis-reports the default selected property of an option -// Accessing the parent's selectedIndex property fixes it -if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - var parent = elem.parentNode; - - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }; -} - -jQuery.each([ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -}); - -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} - -// Radios and checkboxes getter/setter -jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); - } - } - }; - if ( !jQuery.support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - // Support: Webkit - // "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - }; - } -}); -var rformElems = /^(?:input|select|textarea)$/i, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - var tmp, events, t, handleObjIn, - special, eventHandle, handleObj, - handlers, type, namespaces, origType, - elemData = jQuery._data( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !(events = elemData.events) ) { - events = elemData.events = {}; - } - if ( !(eventHandle = elemData.handle) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( core_rnotwhite ) || [""]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !(handlers = events[ type ]) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - var j, handleObj, tmp, - origCount, t, events, - special, handlers, type, - namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( core_rnotwhite ) || [""]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery._removeData( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - var handle, ontype, cur, - bubbleType, special, tmp, i, - eventPath = [ elem || document ], - type = core_hasOwn.call( event, "type" ) ? event.type : event, - namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === (elem.ownerDocument || document) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { - event.preventDefault(); - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && - jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - try { - elem[ type ](); - } catch ( e ) { - // IE<9 dies on focus/blur to hidden element (#1486,#12518) - // only reproducible on winXP IE8 native, not IE9 in IE8 mode - } - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, ret, handleObj, matched, j, - handlerQueue = [], - args = core_slice.call( arguments ), - handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( (event.result = ret) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var sel, handleObj, matches, i, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { - - /* jshint eqeqeq: false */ - for ( ; cur != this; cur = cur.parentNode || this ) { - /* jshint eqeqeq: true */ - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); - } - - return handlerQueue; - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: IE<9 - // Fix target property (#1925) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Support: Chrome 23+, Safari? - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Support: IE<9 - // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) - event.metaKey = !!event.metaKey; - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var body, eventDoc, doc, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - try { - this.focus(); - return false; - } catch ( e ) { - // Support: IE<9 - // If we error on focus to hidden element (#1486, #12518), - // let .trigger() run the handlers - } - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Even when returnValue equals to undefined Firefox will still show alert - if ( event.result !== undefined ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - var name = "on" + type; - - if ( elem.detachEvent ) { - - // #8545, #7054, preventing memory leaks for custom events in IE6-8 - // detachEvent needed property on element, by name of that event, to properly expose it to GC - if ( typeof elem[ name ] === core_strundefined ) { - elem[ name ] = null; - } - - elem.detachEvent( name, handle ); - } - }; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - if ( !e ) { - return; - } - - // If preventDefault exists, run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // Support: IE - // Otherwise set the returnValue property of the original event to false - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - if ( !e ) { - return; - } - // If stopPropagation exists, run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - - // Support: IE - // Set the cancelBubble property of the original event to true - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// IE submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !jQuery._data( form, "submitBubbles" ) ) { - jQuery.event.add( form, "submit._submit", function( event ) { - event._submit_bubble = true; - }); - jQuery._data( form, "submitBubbles", true ); - } - }); - // return undefined since we don't need an event listener - }, - - postDispatch: function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( event._submit_bubble ) { - delete event._submit_bubble; - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - } - }, - - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !jQuery.support.changeBubbles ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - } - // Allow triggered, simulated change events (#11500) - jQuery.event.simulate( "change", this, event, true ); - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - jQuery._data( elem, "changeBubbles", true ); - } - }); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return !rformElems.test( this.nodeName ); - } - }; -} - -// Create "bubbling" focus and blur events -if ( !jQuery.support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var type, origFn; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - var elem = this[0]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -}); -var isSimple = /^.[^:#\[\.,]*$/, - rparentsprev = /^(?:parents|prev(?:Until|All))/, - rneedsContext = jQuery.expr.match.needsContext, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var i, - ret = [], - self = this, - len = self.length; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, - - has: function( target ) { - var i, - targets = jQuery( target, this ), - len = targets.length; - - return this.filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector || [], true) ); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector || [], false) ); - }, - - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - ret = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { - // Always skip document fragments - if ( cur.nodeType < 11 && (pos ? - pos.index(cur) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector(cur, selectors)) ) { - - cur = ret.push( cur ); - break; - } - } - } - - return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( jQuery.unique(all) ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -function sibling( cur, dir ) { - do { - cur = cur[ dir ]; - } while ( cur && cur.nodeType !== 1 ); - - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - if ( this.length > 1 ) { - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - ret = jQuery.unique( ret ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - } - - return this.pushStack( ret ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - })); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - }); - - } - - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - }); - - } - - if ( typeof qualifier === "string" ) { - if ( isSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); - } - - return jQuery.grep( elements, function( elem ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; - }); -} -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rtbody = /\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - option: [ 1, "" ], - legend: [ 1, "
      ", "
      " ], - area: [ 1, "", "" ], - param: [ 1, "", "" ], - thead: [ 1, "", "
      " ], - tr: [ 2, "", "
      " ], - col: [ 2, "", "
      " ], - td: [ 3, "", "
      " ], - - // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, - // unless wrapped in a div with non-breaking characters in front of it. - _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
      ", "
      " ] - }, - safeFragment = createSafeFragment( document ), - fragmentDiv = safeFragment.appendChild( document.createElement("div") ); - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -jQuery.fn.extend({ - text: function( value ) { - return jQuery.access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); - }, null, value, arguments.length ); - }, - - append: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - }); - }, - - before: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - }); - }, - - after: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - }); - }, - - // keepData is for internal use only--do not document - remove: function( selector, keepData ) { - var elem, - elems = selector ? jQuery.filter( selector, this ) : this, - i = 0; - - for ( ; (elem = elems[i]) != null; i++ ) { - - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem ) ); - } - - if ( elem.parentNode ) { - if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { - setGlobalEval( getAll( elem, "script" ) ); - } - elem.parentNode.removeChild( elem ); - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - - // If this is a select, ensure that it displays empty (#12336) - // Support: IE<9 - if ( elem.options && jQuery.nodeName( elem, "select" ) ) { - elem.options.length = 0; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function () { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return jQuery.access( this, function( value ) { - var elem = this[0] || {}, - i = 0, - l = this.length; - - if ( value === undefined ) { - return elem.nodeType === 1 ? - elem.innerHTML.replace( rinlinejQuery, "" ) : - undefined; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && - ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && - !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1>" ); - - try { - for (; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - elem = this[i] || {}; - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch(e) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var - // Snapshot the DOM in case .domManip sweeps something relevant into its fragment - args = jQuery.map( this, function( elem ) { - return [ elem.nextSibling, elem.parentNode ]; - }), - i = 0; - - // Make the changes, replacing each context element with the new content - this.domManip( arguments, function( elem ) { - var next = args[ i++ ], - parent = args[ i++ ]; - - if ( parent ) { - // Don't use the snapshot next if it has moved (#13810) - if ( next && next.parentNode !== parent ) { - next = this.nextSibling; - } - jQuery( this ).remove(); - parent.insertBefore( elem, next ); - } - // Allow new content to include elements from the context set - }, true ); - - // Force removal if there was no new content (e.g., from empty arguments) - return i ? this : this.remove(); - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, callback, allowIntersection ) { - - // Flatten any nested arrays - args = core_concat.apply( [], args ); - - var first, node, hasScripts, - scripts, doc, fragment, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[0], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { - return this.each(function( index ) { - var self = set.eq( index ); - if ( isFunction ) { - args[0] = value.call( this, index, self.html() ); - } - self.domManip( args, callback, allowIntersection ); - }); - } - - if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( this[i], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - - if ( node.src ) { - // Hope ajax is available... - jQuery._evalUrl( node.src ); - } else { - jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); - } - } - } - } - - // Fix #11809: Avoid leaking memory - fragment = first = null; - } - } - - return this; - } -}); - -// Support: IE<8 -// Manipulating tables requires a tbody -function manipulationTarget( elem, content ) { - return jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ? - - elem.getElementsByTagName("tbody")[0] || - elem.appendChild( elem.ownerDocument.createElement("tbody") ) : - elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - if ( match ) { - elem.type = match[1]; - } else { - elem.removeAttribute("type"); - } - return elem; -} - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var elem, - i = 0; - for ( ; (elem = elems[i]) != null; i++ ) { - jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); - } -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } -} - -function fixCloneNodeIssues( src, dest ) { - var nodeName, e, data; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - nodeName = dest.nodeName.toLowerCase(); - - // IE6-8 copies events bound via attachEvent when using cloneNode. - if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { - data = jQuery._data( dest ); - - for ( e in data.events ) { - jQuery.removeEvent( dest, e, data.handle ); - } - - // Event data gets referenced instead of copied if the expando gets copied too - dest.removeAttribute( jQuery.expando ); - } - - // IE blanks contents when cloning scripts, and tries to evaluate newly-set text - if ( nodeName === "script" && dest.text !== src.text ) { - disableScript( dest ).text = src.text; - restoreScript( dest ); - - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. - } else if ( nodeName === "object" ) { - if ( dest.parentNode ) { - dest.outerHTML = src.outerHTML; - } - - // This path appears unavoidable for IE9. When cloning an object - // element in IE9, the outerHTML strategy above is not sufficient. - // If the src has innerHTML and the destination does not, - // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { - dest.innerHTML = src.innerHTML; - } - - } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - - dest.defaultChecked = dest.checked = src.checked; - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.defaultSelected = dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - i = 0, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone(true); - jQuery( insert[i] )[ original ]( elems ); - - // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() - core_push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -}); - -function getAll( context, tag ) { - var elems, elem, - i = 0, - found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : - undefined; - - if ( !found ) { - for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { - if ( !tag || jQuery.nodeName( elem, tag ) ) { - found.push( elem ); - } else { - jQuery.merge( found, getAll( elem, tag ) ); - } - } - } - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], found ) : - found; -} - -// Used in buildFragment, fixes the defaultChecked property -function fixDefaultChecked( elem ) { - if ( manipulation_rcheckableType.test( elem.type ) ) { - elem.defaultChecked = elem.checked; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var destElements, node, clone, i, srcElements, - inPage = jQuery.contains( elem.ownerDocument, elem ); - - if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { - clone = elem.cloneNode( true ); - - // IE<=8 does not properly clone detached, unknown element nodes - } else { - fragmentDiv.innerHTML = elem.outerHTML; - fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); - } - - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - // Fix all IE cloning issues - for ( i = 0; (node = srcElements[i]) != null; ++i ) { - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[i] ) { - fixCloneNodeIssues( node, destElements[i] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0; (node = srcElements[i]) != null; i++ ) { - cloneCopyEvent( node, destElements[i] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - destElements = srcElements = node = null; - - // Return the cloned set - return clone; - }, - - buildFragment: function( elems, context, scripts, selection ) { - var j, elem, contains, - tmp, tag, tbody, wrap, - l = elems.length, - - // Ensure a safe fragment - safe = createSafeFragment( context ), - - nodes = [], - i = 0; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || safe.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - - tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; - - // Descend through wrappers to the right content - j = wrap[0]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Manually add leading whitespace removed by IE - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); - } - - // Remove IE's autoinserted from table fragments - if ( !jQuery.support.tbody ) { - - // String was a , *may* have spurious - elem = tag === "table" && !rtbody.test( elem ) ? - tmp.firstChild : - - // String was a bare or - wrap[1] === "
      " && !rtbody.test( elem ) ? - tmp : - 0; - - j = elem && elem.childNodes.length; - while ( j-- ) { - if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { - elem.removeChild( tbody ); - } - } - } - - jQuery.merge( nodes, tmp.childNodes ); - - // Fix #12392 for WebKit and IE > 9 - tmp.textContent = ""; - - // Fix #12392 for oldIE - while ( tmp.firstChild ) { - tmp.removeChild( tmp.firstChild ); - } - - // Remember the top-level container for proper cleanup - tmp = safe.lastChild; - } - } - } - - // Fix #11356: Clear elements from fragment - if ( tmp ) { - safe.removeChild( tmp ); - } - - // Reset defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - if ( !jQuery.support.appendChecked ) { - jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); - } - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( safe.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - tmp = null; - - return safe; - }, - - cleanData: function( elems, /* internal */ acceptData ) { - var elem, type, id, data, - i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, - deleteExpando = jQuery.support.deleteExpando, - special = jQuery.event.special; - - for ( ; (elem = elems[i]) != null; i++ ) { - - if ( acceptData || jQuery.acceptData( elem ) ) { - - id = elem[ internalKey ]; - data = id && cache[ id ]; - - if ( data ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - - delete cache[ id ]; - - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( deleteExpando ) { - delete elem[ internalKey ]; - - } else if ( typeof elem.removeAttribute !== core_strundefined ) { - elem.removeAttribute( internalKey ); - - } else { - elem[ internalKey ] = null; - } - - core_deletedIds.push( id ); - } - } - } - } - }, - - _evalUrl: function( url ) { - return jQuery.ajax({ - url: url, - type: "GET", - dataType: "script", - async: false, - global: false, - "throws": true - }); - } -}); -jQuery.fn.extend({ - wrapAll: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapAll( html.call(this, i) ); - }); - } - - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); - - if ( this[0].parentNode ) { - wrap.insertBefore( this[0] ); - } - - wrap.map(function() { - var elem = this; - - while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { - elem = elem.firstChild; - } - - return elem; - }).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapInner( html.call(this, i) ); - }); - } - - return this.each(function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - }); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each(function(i) { - jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); - }); - }, - - unwrap: function() { - return this.parent().each(function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - }).end(); - } -}); -var iframe, getStyles, curCSS, - ralpha = /alpha\([^)]*\)/i, - ropacity = /opacity\s*=\s*([^)]*)/, - rposition = /^(top|right|bottom|left)$/, - // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" - // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rmargin = /^margin/, - rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), - rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), - rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), - elemdisplay = { BODY: "block" }, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: 0, - fontWeight: 400 - }, - - cssExpand = [ "Top", "Right", "Bottom", "Left" ], - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; - -// return a css property mapped to a potentially vendor prefixed property -function vendorPropName( style, name ) { - - // shortcut for names that are not vendor prefixed - if ( name in style ) { - return name; - } - - // check for vendor prefixed names - var capName = name.charAt(0).toUpperCase() + name.slice(1), - origName = name, - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in style ) { - return name; - } - } - - return origName; -} - -function isHidden( elem, el ) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); -} - -function showHide( elements, show ) { - var display, elem, hidden, - values = [], - index = 0, - length = elements.length; - - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - values[ index ] = jQuery._data( elem, "olddisplay" ); - display = elem.style.display; - if ( show ) { - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !values[ index ] && display === "none" ) { - elem.style.display = ""; - } - - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( elem.style.display === "" && isHidden( elem ) ) { - values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); - } - } else { - - if ( !values[ index ] ) { - hidden = isHidden( elem ); - - if ( display && display !== "none" || !hidden ) { - jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); - } - } - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( index = 0; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - if ( !show || elem.style.display === "none" || elem.style.display === "" ) { - elem.style.display = show ? values[ index ] || "" : "none"; - } - } - - return elements; -} - -jQuery.fn.extend({ - css: function( name, value ) { - return jQuery.access( this, function( elem, name, value ) { - var len, styles, - map = {}, - i = 0; - - if ( jQuery.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - }, - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each(function() { - if ( isHidden( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - }); - } -}); - -jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "columnCount": true, - "fillOpacity": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - style = elem.style; - - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // convert relative number strings (+= or -=) to relative numbers. #7345 - if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); - // Fixes bug #9237 - type = "number"; - } - - // Make sure that NaN and null values aren't set. See: #7116 - if ( value == null || type === "number" && isNaN( value ) ) { - return; - } - - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( type === "number" && !jQuery.cssNumber[ origName ] ) { - value += "px"; - } - - // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, - // but it would mean to define eight (for every problematic property) identical functions - if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { - - // Wrapped to prevent IE from throwing errors when 'invalid' values are provided - // Fixes bug #5509 - try { - style[ name ] = value; - } catch(e) {} - } - - } else { - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var num, val, hooks, - origName = jQuery.camelCase( name ); - - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - //convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Return, converting to number if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; - } - return val; - } -}); - -// NOTE: we've included the "window" in window.getComputedStyle -// because jsdom on node.js will break without it. -if ( window.getComputedStyle ) { - getStyles = function( elem ) { - return window.getComputedStyle( elem, null ); - }; - - curCSS = function( elem, name, _computed ) { - var width, minWidth, maxWidth, - computed = _computed || getStyles( elem ), - - // getPropertyValue is only needed for .css('filter') in IE9, see #12537 - ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, - style = elem.style; - - if ( computed ) { - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right - // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels - // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values - if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret; - }; -} else if ( document.documentElement.currentStyle ) { - getStyles = function( elem ) { - return elem.currentStyle; - }; - - curCSS = function( elem, name, _computed ) { - var left, rs, rsLeft, - computed = _computed || getStyles( elem ), - ret = computed ? computed[ name ] : undefined, - style = elem.style; - - // Avoid setting ret to empty string here - // so we don't default to auto - if ( ret == null && style && style[ name ] ) { - ret = style[ name ]; - } - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - // but not position css attributes, as those are proportional to the parent element instead - // and we can't measure the parent instead because it might trigger a "stacking dolls" problem - if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { - - // Remember the original values - left = style.left; - rs = elem.runtimeStyle; - rsLeft = rs && rs.left; - - // Put in the new values to get a computed value out - if ( rsLeft ) { - rs.left = elem.currentStyle.left; - } - style.left = name === "fontSize" ? "1em" : ret; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - if ( rsLeft ) { - rs.left = rsLeft; - } - } - - return ret === "" ? "auto" : ret; - }; -} - -function setPositiveNumber( elem, value, subtract ) { - var matches = rnumsplit.exec( value ); - return matches ? - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i = extra === ( isBorderBox ? "border" : "content" ) ? - // If we already have the right measurement, avoid augmentation - 4 : - // Otherwise initialize for horizontal or vertical properties - name === "width" ? 1 : 0, - - val = 0; - - for ( ; i < 4; i += 2 ) { - // both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // at this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - // at this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // at this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with offset property, which is equivalent to the border-box value - var valueIsBorderBox = true, - val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - styles = getStyles( elem ), - isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if ( val <= 0 || val == null ) { - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, styles ); - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - } - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test(val) ) { - return val; - } - - // we need the check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - } - - // use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -// Try to determine the default display value of an element -function css_defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - // Use the already-created iframe if possible - iframe = ( iframe || - jQuery("