{"record":{"id":"81ccbc78de4ee7a6","repo":"google/ExoPlayer","slug":"missing-implementation-to-handle-command-change-me","errorCode":null,"errorMessage":"Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS","messagePattern":"Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java","lineNumber":3198,"sourceCode":"  protected ListenableFuture<?> handleSetMediaItems(\n      List<MediaItem> mediaItems, int startIndex, long startPositionMs) {\n    throw new IllegalStateException(\"Missing implementation to handle COMMAND_SET_MEDIA_ITEM(S)\");\n  }\n\n  /**\n   * Handles calls to {@link Player#addMediaItem} and {@link Player#addMediaItems}.\n   *\n   * <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.\n   *\n   * @param index The index at which to add the items. The index is in the range 0 &lt;= {@code\n   *     index} &lt;= {@link #getMediaItemCount()}.\n   * @param mediaItems The media items to add.\n   * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}\n   *     changes caused by this call.\n   */\n  @ForOverride\n  protected ListenableFuture<?> handleAddMediaItems(int index, List<MediaItem> mediaItems) {\n    throw new IllegalStateException(\"Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS\");\n  }\n\n  /**\n   * Handles calls to {@link Player#moveMediaItem} and {@link Player#moveMediaItems}.\n   *\n   * <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.\n   *\n   * @param fromIndex The start index of the items to move. The index is in the range 0 &lt;= {@code\n   *     fromIndex} &lt; {@link #getMediaItemCount()}.\n   * @param toIndex The index of the first item not to be included in the move (exclusive). The\n   *     index is in the range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link\n   *     #getMediaItemCount()}.\n   * @param newIndex The new index of the first moved item. The index is in the range {@code 0}\n   *     &lt;= {@code newIndex} &lt; {@link #getMediaItemCount() - (toIndex - fromIndex)}.\n   * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}\n   *     changes caused by this call.\n   */\n  @ForOverride","sourceCodeStart":3180,"sourceCodeEnd":3216,"githubUrl":"https://github.com/google/ExoPlayer/blob/dd430f7053a1a3958deea3ead6a0565150c06bfc/library/common/src/main/java/com/google/android/exoplayer2/SimpleBasePlayer.java#L3180-L3216","documentation":"Player.addMediaItem/addMediaItems route through SimpleBasePlayer.handleAddMediaItems(int index, List<MediaItem>). The default throws IllegalStateException when COMMAND_CHANGE_MEDIA_ITEMS appears in availableCommands without the override. The handler is only invoked if the player is not in STATE_IDLE (SimpleBasePlayer requires a prepared state before playlist mutation).","triggerScenarios":"Calling player.addMediaItem(item) or addMediaItems(index, items) on a custom SimpleBasePlayer that declares COMMAND_CHANGE_MEDIA_ITEMS but does not override handleAddMediaItems.","commonSituations":"Implementing setMediaItems for basic playback but not the incremental add variants; queue-building apps (podcast/video queues) calling addMediaItem repeatedly against a wrapper player.","solutions":["Override handleAddMediaItems(int index, List<MediaItem> mediaItems): splice the items into the existing playlist at index and return an immediate future after invalidating state.","Or stop advertising COMMAND_CHANGE_MEDIA_ITEMS if the playlist must be immutable.","Implement add, move, remove and replace handlers as a unit since they share the same command."],"exampleFix":"// before\n// availableCommands includes COMMAND_CHANGE_MEDIA_ITEMS, no override:\n\n// after\n@Override\nprotected ListenableFuture<?> handleAddMediaItems(int index, List<MediaItem> mediaItems) {\n  List<MediaItem> playlist = new ArrayList<>(state.playlist);\n  playlist.addAll(index, mediaItems);\n  state = state.buildUpon().setPlaylist(playlist).build();\n  invalidateState();\n  return Futures.immediateVoidFuture();\n}","handlingStrategy":"validation","validationCode":"if (player.isCommandAvailable(Player.COMMAND_CHANGE_MEDIA_ITEMS)) {\n  checkOverrides(playerClass, \"handleAddMediaItems\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Implement add, move, remove and replace handlers together — one command gates them all.","Remember playlist mutation handlers are only called when not in STATE_IDLE.","Clamp or verify the index argument against getMediaItemCount() in your backing store."],"tags":["exoplayer","media3","player","playlist","subclassing"],"backgroundTag":null,"analyzedSha":"dd430f7053a1a3958deea3ead6a0565150c06bfc","analyzedAt":"2026-08-14T12:22:02.982Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}