| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.videoeditor.service; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import android.media.videoeditor.MediaProperties; |
| import android.media.videoeditor.MediaVideoItem; |
| import android.media.videoeditor.VideoEditor; |
| import android.media.videoeditor.VideoEditor.PreviewProgressListener; |
| import android.net.Uri; |
| import android.util.Xml; |
| import android.view.SurfaceHolder; |
| |
| |
| /** |
| * The video editor project encapsulates the video editor and the project metadata. |
| */ |
| public class VideoEditorProject { |
| // The name of the metadata file |
| private final static String PROJECT_METADATA_FILENAME = "metadata.xml"; |
| |
| public static final int DEFAULT_ZOOM_LEVEL = 20; |
| |
| // XML definitions |
| private static final String TAG_PROJECT = "project"; |
| private static final String TAG_MOVIE = "movie"; |
| private static final String TAG_DOWNLOAD = "download"; |
| private static final String ATTR_NAME = "name"; |
| private static final String ATTR_URI = "uri"; |
| private static final String ATTR_SAVED = "saved"; |
| private static final String ATTR_THEME = "theme"; |
| private static final String ATTR_PLAYHEAD_POSITION = "playhead"; |
| private static final String ATTR_DURATION = "duration"; |
| private static final String ATTR_ZOOM_LEVEL = "zoom_level"; |
| private static final String ATTR_MIME = "mime"; |
| private static final String ATTR_FILENAME = "filename"; |
| private static final String ATTR_TIME = "time"; |
| |
| // Instance variables |
| private final VideoEditor mVideoEditor; |
| private final String mProjectPath; |
| private final long mProjectDurationMs; |
| private final List<Download> mDownloads; |
| private String mProjectName; |
| private long mLastSaved; |
| private Uri mExportedMovieUri; |
| private int mAspectRatio; |
| private String mTheme; |
| private long mPlayheadPosMs; |
| private int mZoomLevel; |
| private List<MovieMediaItem> mMediaItems = new ArrayList<MovieMediaItem>(); |
| private List<MovieAudioTrack> mAudioTracks = new ArrayList<MovieAudioTrack>(); |
| private boolean mClean; |
| |
| /** |
| * Download item |
| */ |
| public static class Download { |
| private final String mMediaUri; |
| private final String mMimeType; |
| private final String mFilename; |
| private final long mTime; |
| |
| /** |
| * Constructor |
| * |
| * @param mediaUri The media URI |
| * @param mimeType The mime type |
| * @param filename The filename |
| * @param time The time when the file was downloaded |
| */ |
| private Download(String mediaUri, String mimeType, String filename, long time) { |
| mMediaUri = mediaUri; |
| mMimeType = mimeType; |
| mFilename = filename; |
| mTime = time; |
| } |
| |
| /** |
| * @return the media URI |
| */ |
| public String getMediaUri() { |
| return mMediaUri; |
| } |
| |
| /** |
| * @return the mime type |
| */ |
| public String getMimeType() { |
| return mMimeType; |
| } |
| |
| /** |
| * @return the filename |
| */ |
| public String getFilename() { |
| return mFilename; |
| } |
| |
| /** |
| * @return the mTime |
| */ |
| public long getTime() { |
| return mTime; |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param videoEditor The video editor. Note that this can be null when |
| * we create the project for the purpose of displaying a project preview. |
| * @param projectPath The project path |
| * @param projectName The project name |
| * @param lastSaved Time when project was last saved |
| * @param playheadPosMs The playhead position |
| * @param durationMs The project duration |
| * @param zoomLevel The zoom level |
| * @param exportedMovieUri The exported movie URI |
| * @param theme The project theme |
| * @param downloads The list of downloads |
| */ |
| VideoEditorProject(VideoEditor videoEditor, String projectPath, String projectName, |
| long lastSaved, long playheadPosMs, long durationMs, int zoomLevel, |
| Uri exportedMovieUri, String theme, List<Download> downloads) { |
| mVideoEditor = videoEditor; |
| if (videoEditor != null) { |
| mAspectRatio = videoEditor.getAspectRatio(); |
| } |
| |
| if (downloads != null) { |
| mDownloads = downloads; |
| } else { |
| mDownloads = new ArrayList<Download>(); |
| } |
| mProjectPath = projectPath; |
| mProjectName = projectName; |
| mLastSaved = lastSaved; |
| mPlayheadPosMs = playheadPosMs; |
| mProjectDurationMs = durationMs; |
| mZoomLevel = zoomLevel; |
| mExportedMovieUri = exportedMovieUri; |
| mTheme = theme; |
| mClean = true; |
| } |
| |
| /** |
| * @param clean true if this is clean |
| */ |
| public void setClean(boolean clean) { |
| mClean = clean; |
| } |
| |
| /** |
| * @return true if no change was made |
| */ |
| public boolean isClean() { |
| return mClean; |
| } |
| |
| /** |
| * @return The project path |
| */ |
| public String getPath() { |
| return mProjectPath; |
| } |
| |
| /** |
| * @param projectName The project name |
| */ |
| public void setProjectName(String projectName) { |
| mProjectName = projectName; |
| mClean = false; |
| } |
| |
| /** |
| * @return The project name |
| */ |
| public String getName() { |
| return mProjectName; |
| } |
| |
| /** |
| * @return Time when time was last saved |
| */ |
| public long getLastSaved() { |
| return mLastSaved; |
| } |
| |
| /** |
| * @return The project duration. |
| * |
| * Note: This method should only be called to retrieve the project duration |
| * as saved on disk. Once a project is opened call computeDuration() to get |
| * the current duration. |
| */ |
| public long getProjectDuration() { |
| return mProjectDurationMs; |
| } |
| |
| /** |
| * @return The zoom level |
| */ |
| public int getZoomLevel() { |
| return mZoomLevel; |
| } |
| |
| /** |
| * @param zoomLevel The zoom level |
| */ |
| public void setZoomLevel(int zoomLevel) { |
| mZoomLevel = zoomLevel; |
| } |
| |
| /** |
| * @return The aspect ratio |
| */ |
| public int getAspectRatio() { |
| return mAspectRatio; |
| } |
| |
| /** |
| * @return The playhead position |
| */ |
| public long getPlayheadPos() { |
| return mPlayheadPosMs; |
| } |
| |
| /** |
| * @param playheadPosMs The playhead position |
| */ |
| public void setPlayheadPos(long playheadPosMs) { |
| mPlayheadPosMs = playheadPosMs; |
| } |
| |
| /** |
| * @param aspectRatio The aspect ratio |
| */ |
| void setAspectRatio(int aspectRatio) { |
| mAspectRatio = aspectRatio; |
| mClean = false; |
| } |
| |
| /** |
| * Add the URI of an exported movie |
| * |
| * @param uri The movie URI |
| */ |
| void addExportedMovieUri(Uri uri) { |
| mExportedMovieUri = uri; |
| mClean = false; |
| } |
| |
| /** |
| * @return The exported movie URI |
| */ |
| public Uri getExportedMovieUri() { |
| return mExportedMovieUri; |
| } |
| |
| /** |
| * @param theme The theme |
| */ |
| void setTheme(String theme) { |
| mTheme = theme; |
| mClean = false; |
| } |
| |
| /** |
| * @return The theme |
| */ |
| public String getTheme() { |
| return mTheme; |
| } |
| |
| /** |
| * Set the media items |
| * |
| * @param mediaItems The media items |
| */ |
| void setMediaItems(List<MovieMediaItem> mediaItems) { |
| mMediaItems = mediaItems; |
| mClean = false; |
| } |
| |
| /** |
| * Insert a media item after the specified media item id |
| * |
| * @param mediaItem The media item |
| * @param afterMediaItemId Insert after this media item id |
| */ |
| void insertMediaItem(MovieMediaItem mediaItem, String afterMediaItemId) { |
| if (afterMediaItemId == null) { |
| if (mMediaItems.size() > 0) { |
| // Invalidate the transition at the beginning of the timeline |
| final MovieMediaItem firstMediaItem = mMediaItems.get(0); |
| if (firstMediaItem.getBeginTransition() != null) { |
| firstMediaItem.setBeginTransition(null); |
| } |
| } |
| |
| mMediaItems.add(0, mediaItem); |
| mClean = false; |
| } else { |
| final int mediaItemCount = mMediaItems.size(); |
| for (int i = 0; i < mediaItemCount; i++) { |
| final MovieMediaItem mi = mMediaItems.get(i); |
| if (mi.getId().equals(afterMediaItemId)) { |
| // Invalidate the transition at the end of this media item |
| mi.setEndTransition(null); |
| // Invalidate the reference in the next media item (if any) |
| if (i < mediaItemCount - 1) { |
| mMediaItems.get(i + 1).setBeginTransition(null); |
| } |
| |
| // Insert the new media item |
| mMediaItems.add(i + 1, mediaItem); |
| mClean = false; |
| return; |
| } |
| } |
| |
| throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId); |
| } |
| } |
| |
| /** |
| * Update the specified media item |
| * |
| * @param newMediaItem The media item can be a new instance of the media |
| * item or an updated version of the same instance. |
| */ |
| void updateMediaItem(MovieMediaItem newMediaItem) { |
| final String newMediaItemId = newMediaItem.getId(); |
| final int count = mMediaItems.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| if (mediaItem.getId().equals(newMediaItemId)) { |
| mMediaItems.set(i, newMediaItem); |
| mClean = false; |
| // Update the transitions of the previous and next item |
| if (i > 0) { |
| final MovieMediaItem prevMediaItem = mMediaItems.get(i - 1); |
| prevMediaItem.setEndTransition(newMediaItem.getBeginTransition()); |
| } |
| |
| if (i < count - 1) { |
| final MovieMediaItem nextMediaItem = mMediaItems.get(i + 1); |
| nextMediaItem.setBeginTransition(newMediaItem.getEndTransition()); |
| } |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Remove the specified media item |
| * |
| * @param mediaItemId The media item id |
| * @param transition The transition to be set between at the delete |
| * position |
| */ |
| void removeMediaItem(String mediaItemId, MovieTransition transition) { |
| String prevMediaItemId = null; |
| final int count = mMediaItems.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| if (mediaItem.getId().equals(mediaItemId)) { |
| mMediaItems.remove(i); |
| mClean = false; |
| if (transition != null) { |
| addTransition(transition, prevMediaItemId); |
| } else { |
| if (i > 0) { |
| final MovieMediaItem prevMediaItem = mMediaItems.get(i - 1); |
| prevMediaItem.setEndTransition(null); |
| } |
| |
| if (i < count - 1) { |
| final MovieMediaItem nextMediaItem = mMediaItems.get(i); |
| nextMediaItem.setBeginTransition(null); |
| } |
| } |
| break; |
| } |
| |
| prevMediaItemId = mediaItem.getId(); |
| } |
| } |
| |
| /** |
| * @return The media items list |
| */ |
| public List<MovieMediaItem> getMediaItems() { |
| return mMediaItems; |
| } |
| |
| /** |
| * @return The media item count |
| */ |
| public int getMediaItemCount() { |
| return mMediaItems.size(); |
| } |
| |
| /** |
| * @param mediaItemId The media item id |
| * |
| * @return The media item |
| */ |
| public MovieMediaItem getMediaItem(String mediaItemId) { |
| for (MovieMediaItem mediaItem : mMediaItems) { |
| if (mediaItem.getId().equals(mediaItemId)) { |
| return mediaItem; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @return The first media item |
| */ |
| public MovieMediaItem getFirstMediaItem() { |
| if (mMediaItems.size() == 0) { |
| return null; |
| } else { |
| return mMediaItems.get(0); |
| } |
| } |
| |
| /** |
| * Check if the specified media item id is the first media item |
| * |
| * @param mediaItemId The media item id |
| * |
| * @return true if this is the first media item |
| */ |
| public boolean isFirstMediaItem(String mediaItemId) { |
| final MovieMediaItem mediaItem = getFirstMediaItem(); |
| if (mediaItem == null) { |
| return false; |
| } else { |
| return mediaItem.getId().equals(mediaItemId); |
| } |
| } |
| |
| /** |
| * @return The last media item. {@code null} if no item is in the project. |
| */ |
| public MovieMediaItem getLastMediaItem() { |
| final int count = mMediaItems.size(); |
| if (count == 0) { |
| return null; |
| } else { |
| return mMediaItems.get(count - 1); |
| } |
| } |
| |
| /** |
| * Gets the id of the last media item in this project. |
| * |
| * @return Id of the last media item. {@code null} if no item is in this project. |
| */ |
| public String getLastMediaItemId() { |
| MovieMediaItem lastMediaItem = getLastMediaItem(); |
| if (lastMediaItem != null) |
| return lastMediaItem.getId(); |
| return null; |
| } |
| |
| /** |
| * Check if the specified media item id is the last media item |
| * |
| * @param mediaItemId The media item id |
| * |
| * @return true if this is the last media item |
| */ |
| public boolean isLastMediaItem(String mediaItemId) { |
| final MovieMediaItem mediaItem = getLastMediaItem(); |
| if (mediaItem == null) { |
| return false; |
| } else { |
| return mediaItem.getId().equals(mediaItemId); |
| } |
| } |
| |
| /** |
| * Find the previous media item with the specified id |
| * |
| * @param mediaItemId The media item id |
| * @return The previous media item |
| */ |
| public MovieMediaItem getPreviousMediaItem(String mediaItemId) { |
| MovieMediaItem prevMediaItem = null; |
| for (MovieMediaItem mediaItem : mMediaItems) { |
| if (mediaItemId.equals(mediaItem.getId())) { |
| break; |
| } else { |
| prevMediaItem = mediaItem; |
| } |
| } |
| |
| return prevMediaItem; |
| } |
| |
| /** |
| * Find the next media item with the specified id |
| * |
| * @param mediaItemId The media item id |
| * @return The next media item |
| */ |
| public MovieMediaItem getNextMediaItem(String mediaItemId) { |
| boolean getNext = false; |
| final int count = mMediaItems.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mi = mMediaItems.get(i); |
| if (getNext) { |
| return mi; |
| } else { |
| if (mediaItemId.equals(mi.getId())) { |
| getNext = true; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get the previous media item |
| * |
| * @param positionMs The current position in ms |
| * @return The previous media item |
| */ |
| public MovieMediaItem getPreviousMediaItem(long positionMs) { |
| long startTimeMs = 0; |
| MovieMediaItem prevMediaItem = null; |
| for (MovieMediaItem mediaItem : mMediaItems) { |
| if (positionMs == startTimeMs) { |
| break; |
| } else if (positionMs > startTimeMs |
| && positionMs < startTimeMs + mediaItem.getAppTimelineDuration()) { |
| return mediaItem; |
| } else { |
| prevMediaItem = mediaItem; |
| } |
| |
| startTimeMs += mediaItem.getAppTimelineDuration(); |
| if (mediaItem.getEndTransition() != null) { |
| startTimeMs -= mediaItem.getEndTransition().getAppDuration(); |
| } |
| } |
| |
| return prevMediaItem; |
| } |
| |
| /** |
| * Get the next media item |
| * |
| * @param positionMs The current position in ms |
| * @return The next media item |
| */ |
| public MovieMediaItem getNextMediaItem(long positionMs) { |
| long startTimeMs = 0; |
| final int count = mMediaItems.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| if (positionMs >= startTimeMs |
| && positionMs < startTimeMs + mediaItem.getAppTimelineDuration() - |
| getEndTransitionDuration(mediaItem)) { |
| if (i < count - 1) { |
| return mMediaItems.get(i + 1); |
| } else { |
| return null; |
| } |
| } else if (positionMs >= startTimeMs |
| && positionMs < startTimeMs + mediaItem.getAppTimelineDuration()) { |
| if (i < count - 2) { |
| return mMediaItems.get(i + 2); |
| } else { |
| return null; |
| } |
| } else { |
| startTimeMs += mediaItem.getAppTimelineDuration(); |
| startTimeMs -= getEndTransitionDuration(mediaItem); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get the beginning media item of the specified transition |
| * |
| * @param transition The transition |
| * |
| * @return The media item |
| */ |
| public MovieMediaItem getPreviousMediaItem(MovieTransition transition) { |
| final int count = mMediaItems.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| if (i == 0) { |
| if (mediaItem.getBeginTransition() == transition) { |
| return null; |
| } |
| } |
| |
| if (mediaItem.getEndTransition() == transition) { |
| return mediaItem; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Return the end transition duration |
| * |
| * @param mediaItem The media item |
| * @return the end transition duration |
| */ |
| private static long getEndTransitionDuration(MovieMediaItem mediaItem) { |
| if (mediaItem.getEndTransition() != null) { |
| return mediaItem.getEndTransition().getAppDuration(); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Determine the media item after which a new media item will be inserted. |
| * |
| * @param timeMs The inquiry position |
| * |
| * @return The media item after which the insertion will be performed |
| */ |
| public MovieMediaItem getInsertAfterMediaItem(long timeMs) { |
| long beginMs = 0; |
| long endMs = 0; |
| MovieMediaItem prevMediaItem = null; |
| final int mediaItemsCount = mMediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| |
| endMs = beginMs + mediaItem.getAppTimelineDuration(); |
| |
| if (mediaItem.getEndTransition() != null) { |
| if (i < mediaItemsCount - 1) { |
| endMs -= mediaItem.getEndTransition().getAppDuration(); |
| } |
| } |
| |
| if (timeMs >= beginMs && timeMs <= endMs) { |
| if (timeMs - beginMs < endMs - timeMs) { // Closer to the beginning |
| return prevMediaItem; |
| } else { // Closer to the end |
| return mediaItem; // Insert after this item |
| } |
| } |
| |
| beginMs = endMs; |
| prevMediaItem = mediaItem; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @return true if media items have different aspect ratios |
| */ |
| public boolean hasMultipleAspectRatios() { |
| int aspectRatio = MediaProperties.ASPECT_RATIO_UNDEFINED; |
| for (MovieMediaItem mediaItem : mMediaItems) { |
| if (aspectRatio == MediaProperties.ASPECT_RATIO_UNDEFINED) { |
| aspectRatio = mediaItem.getAspectRatio(); |
| } else if (mediaItem.getAspectRatio() != aspectRatio) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @return The list of unique aspect ratios |
| */ |
| public ArrayList<Integer> getUniqueAspectRatiosList() { |
| final ArrayList<Integer> aspectRatiosList = new ArrayList<Integer>(); |
| for (MovieMediaItem mediaItem : mMediaItems) { |
| int aspectRatio = mediaItem.getAspectRatio(); |
| if (!aspectRatiosList.contains(aspectRatio)) { |
| aspectRatiosList.add(aspectRatio); |
| } |
| } |
| |
| return aspectRatiosList; |
| } |
| |
| /** |
| * Add a new transition |
| * |
| * @param transition The transition |
| * @param afterMediaItemId Add the transition after this media item |
| */ |
| void addTransition(MovieTransition transition, String afterMediaItemId) { |
| final int count = mMediaItems.size(); |
| if (afterMediaItemId != null) { |
| MovieMediaItem afterMediaItem = null; |
| int afterMediaItemIndex = -1; |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| if (mediaItem.getId().equals(afterMediaItemId)) { |
| afterMediaItem = mediaItem; |
| afterMediaItemIndex = i; |
| break; |
| } |
| } |
| |
| // Link the transition to the next and previous media items |
| if (afterMediaItem == null) { |
| throw new IllegalArgumentException("Media item not found: " + afterMediaItemId); |
| } |
| |
| afterMediaItem.setEndTransition(transition); |
| |
| if (afterMediaItemIndex < count - 1) { |
| final MovieMediaItem beforeMediaItem = mMediaItems.get(afterMediaItemIndex + 1); |
| beforeMediaItem.setBeginTransition(transition); |
| } |
| } else { |
| if (count == 0) { |
| throw new IllegalArgumentException("Media item not found at the beginning"); |
| } |
| |
| final MovieMediaItem beforeMediaItem = mMediaItems.get(0); |
| beforeMediaItem.setBeginTransition(transition); |
| } |
| |
| mClean = false; |
| } |
| |
| /** |
| * Remove the specified transition |
| * |
| * @param transitionId The transition id |
| */ |
| void removeTransition(String transitionId) { |
| final int count = mMediaItems.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| final MovieTransition beginTransition = mediaItem.getBeginTransition(); |
| if (beginTransition != null && beginTransition.getId().equals(transitionId)) { |
| mediaItem.setBeginTransition(null); |
| break; |
| } |
| |
| final MovieTransition endTransition = mediaItem.getEndTransition(); |
| if (endTransition != null && endTransition.getId().equals(transitionId)) { |
| mediaItem.setEndTransition(null); |
| } |
| } |
| |
| mClean = false; |
| } |
| |
| /** |
| * Find the transition with the specified id |
| * |
| * @param transitionId The transition id |
| * @return The transition |
| */ |
| public MovieTransition getTransition(String transitionId) { |
| final MovieMediaItem firstMediaItem = getFirstMediaItem(); |
| if (firstMediaItem == null) { |
| return null; |
| } |
| |
| final MovieTransition beginTransition = firstMediaItem.getBeginTransition(); |
| if (beginTransition != null && beginTransition.getId().equals(transitionId)) { |
| return beginTransition; |
| } |
| |
| for (MovieMediaItem mediaItem : mMediaItems) { |
| final MovieTransition endTransition = mediaItem.getEndTransition(); |
| if (endTransition != null && endTransition.getId().equals(transitionId)) { |
| return endTransition; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Add the overlay |
| * |
| * @param mediaItemId The media item id |
| * @param overlay The overlay |
| */ |
| void addOverlay(String mediaItemId, MovieOverlay overlay) { |
| final MovieMediaItem mediaItem = getMediaItem(mediaItemId); |
| |
| // Remove an existing overlay (if any) |
| final MovieOverlay oldOverlay = mediaItem.getOverlay(); |
| if (oldOverlay != null) { |
| mediaItem.removeOverlay(oldOverlay.getId()); |
| } |
| |
| mediaItem.addOverlay(overlay); |
| mClean = false; |
| } |
| |
| /** |
| * Remove the specified overlay |
| * |
| * @param mediaItemId The media item id |
| * @param overlayId The overlay id |
| */ |
| void removeOverlay(String mediaItemId, String overlayId) { |
| final MovieMediaItem mediaItem = getMediaItem(mediaItemId); |
| mediaItem.removeOverlay(overlayId); |
| mClean = false; |
| } |
| |
| /** |
| * Get the specified overlay |
| * |
| * @param mediaItemId The media item id |
| * @param overlayId The overlay id |
| * @return The movie overlay |
| */ |
| public MovieOverlay getOverlay(String mediaItemId, String overlayId) { |
| final MovieMediaItem mediaItem = getMediaItem(mediaItemId); |
| return mediaItem.getOverlay(); |
| } |
| |
| /** |
| * Add the effect |
| * |
| * @param mediaItemId The media item id |
| * @param effect The effect |
| */ |
| void addEffect(String mediaItemId, MovieEffect effect) { |
| final MovieMediaItem mediaItem = getMediaItem(mediaItemId); |
| // Remove an existing effect |
| final MovieEffect oldEffect = mediaItem.getEffect(); |
| if (oldEffect != null) { |
| mediaItem.removeEffect(oldEffect.getId()); |
| } |
| |
| mediaItem.addEffect(effect); |
| mClean = false; |
| } |
| |
| /** |
| * Remove the specified effect |
| * |
| * @param mediaItemId The media item id |
| * @param effectId The effect id |
| */ |
| void removeEffect(String mediaItemId, String effectId) { |
| final MovieMediaItem mediaItem = getMediaItem(mediaItemId); |
| mediaItem.removeEffect(effectId); |
| mClean = false; |
| } |
| |
| /** |
| * Get the specified effect |
| * |
| * @param mediaItemId The media item id |
| * @param effectId The effect id |
| * @return The movie effect |
| */ |
| public MovieEffect getEffect(String mediaItemId, String effectId) { |
| final MovieMediaItem mediaItem = getMediaItem(mediaItemId); |
| return mediaItem.getEffect(); |
| } |
| |
| /** |
| * Set the audio tracks |
| * |
| * @param audioTracks The audio tracks |
| */ |
| void setAudioTracks(List<MovieAudioTrack> audioTracks) { |
| mAudioTracks = audioTracks; |
| mClean = false; |
| } |
| |
| /** |
| * Add an audio track |
| * |
| * @param audioTrack The audio track |
| */ |
| void addAudioTrack(MovieAudioTrack audioTrack) { |
| mAudioTracks.add(audioTrack); |
| mClean = false; |
| } |
| |
| /** |
| * Remove the specified audio track |
| * |
| * @param audioTrackId The audio track id |
| */ |
| void removeAudioTrack(String audioTrackId) { |
| final int count = mAudioTracks.size(); |
| for (int i = 0; i < count; i++) { |
| final MovieAudioTrack audioTrack = mAudioTracks.get(i); |
| if (audioTrack.getId().equals(audioTrackId)) { |
| mAudioTracks.remove(i); |
| mClean = false; |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @return The audio tracks |
| */ |
| public List<MovieAudioTrack> getAudioTracks() { |
| return mAudioTracks; |
| } |
| |
| /** |
| * @param audioTrackId The audio track id |
| * @return The audio track |
| */ |
| public MovieAudioTrack getAudioTrack(String audioTrackId) { |
| for (MovieAudioTrack audioTrack : mAudioTracks) { |
| if (audioTrack.getId().equals(audioTrackId)) { |
| return audioTrack; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Compute the begin time for this media item |
| * |
| * @param mediaItemId The media item id for which we compute the begin time |
| * |
| * @return The begin time for this media item |
| */ |
| public long getMediaItemBeginTime(String mediaItemId) { |
| long beginMs = 0; |
| final int mediaItemsCount = mMediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mi = mMediaItems.get(i); |
| if (mi.getId().equals(mediaItemId)) { |
| break; |
| } |
| |
| beginMs += mi.getAppTimelineDuration(); |
| |
| if (mi.getEndTransition() != null) { |
| if (i < mediaItemsCount - 1) { |
| beginMs -= mi.getEndTransition().getAppDuration(); |
| } |
| } |
| } |
| |
| return beginMs; |
| } |
| |
| /** |
| * @return The total duration |
| */ |
| public long computeDuration() { |
| long totalDurationMs = 0; |
| final int mediaItemsCount = mMediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mediaItem = mMediaItems.get(i); |
| totalDurationMs += mediaItem.getAppTimelineDuration(); |
| |
| if (mediaItem.getEndTransition() != null) { |
| if (i < mediaItemsCount - 1) { |
| totalDurationMs -= mediaItem.getEndTransition().getAppDuration(); |
| } |
| } |
| } |
| |
| return totalDurationMs; |
| } |
| |
| /** |
| * Render a frame according to the preview aspect ratio and activating all |
| * storyboard items relative to the specified time. |
| * |
| * @param surfaceHolder SurfaceHolder used by the application |
| * @param timeMs time corresponding to the frame to display |
| * @param overlayData The overlay data |
| * |
| * @return The accurate time stamp of the frame that is rendered. |
| * @throws IllegalStateException if a preview or an export is already in |
| * progress |
| * @throws IllegalArgumentException if time is negative or beyond the |
| * preview duration |
| */ |
| public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs, |
| VideoEditor.OverlayData overlayData) { |
| if (mVideoEditor != null) { |
| return mVideoEditor.renderPreviewFrame(surfaceHolder, timeMs, overlayData); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Render a frame of a media item. |
| * |
| * @param surfaceHolder SurfaceHolder used by the application |
| * @param mediaItemId The media item id |
| * @param timeMs time corresponding to the frame to display |
| * |
| * @return The accurate time stamp of the frame that is rendered . |
| * @throws IllegalStateException if a preview or an export is already in |
| * progress |
| * @throws IllegalArgumentException if time is negative or beyond the |
| * preview duration |
| */ |
| public long renderMediaItemFrame(SurfaceHolder surfaceHolder, String mediaItemId, |
| long timeMs) { |
| if (mVideoEditor != null) { |
| final MediaVideoItem mediaItem = |
| (MediaVideoItem)mVideoEditor.getMediaItem(mediaItemId); |
| if (mediaItem != null) { |
| return mediaItem.renderFrame(surfaceHolder, timeMs); |
| } else { |
| return -1; |
| } |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Start the preview of all the storyboard items applied on all MediaItems |
| * This method does not block (does not wait for the preview to complete). |
| * The PreviewProgressListener allows to track the progress at the time |
| * interval determined by the callbackAfterFrameCount parameter. The |
| * SurfaceHolder has to be created and ready for use before calling this |
| * method. The method is a no-op if there are no MediaItems in the |
| * storyboard. |
| * |
| * @param surfaceHolder SurfaceHolder where the preview is rendered. |
| * @param fromMs The time (relative to the timeline) at which the preview |
| * will start |
| * @param toMs The time (relative to the timeline) at which the preview will |
| * stop. Use -1 to play to the end of the timeline |
| * @param loop true if the preview should be looped once it reaches the end |
| * @param callbackAfterFrameCount The listener interface should be invoked |
| * after the number of frames specified by this parameter. |
| * @param listener The listener which will be notified of the preview |
| * progress |
| * |
| * @throws IllegalArgumentException if fromMs is beyond the preview duration |
| * @throws IllegalStateException if a preview or an export is already in |
| * progress |
| */ |
| public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, |
| int callbackAfterFrameCount, PreviewProgressListener listener) { |
| if (mVideoEditor != null) { |
| mVideoEditor.startPreview(surfaceHolder, fromMs, toMs, loop, callbackAfterFrameCount, |
| listener); |
| } |
| } |
| |
| /** |
| * Stop the current preview. This method blocks until ongoing preview is |
| * stopped. Ignored if there is no preview running. |
| * |
| * @return The accurate current time when stop is effective expressed in |
| * milliseconds |
| */ |
| public long stopPreview() { |
| if (mVideoEditor != null) { |
| return mVideoEditor.stopPreview(); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Clear the surface |
| * |
| * @param surfaceHolder SurfaceHolder where the preview is rendered. |
| */ |
| public void clearSurface(SurfaceHolder surfaceHolder) { |
| if (mVideoEditor != null) { |
| mVideoEditor.clearSurface(surfaceHolder); |
| } |
| } |
| |
| /** |
| * Release the project |
| */ |
| public void release() { |
| } |
| |
| /** |
| * Add a new download to the project |
| * |
| * @param mediaUri The media URI |
| * @param mimeType The mime type |
| * @param filename The local filename |
| */ |
| public void addDownload(String mediaUri, String mimeType, String filename) { |
| mDownloads.add(new Download(mediaUri, mimeType, filename, System.currentTimeMillis())); |
| mClean = false; |
| } |
| |
| /** |
| * Remove a download |
| * |
| * @param mediaUri The media URI |
| */ |
| public void removeDownload(String mediaUri) { |
| final int count = mDownloads.size(); |
| for (int i = 0; i < count; i++) { |
| final Download download = mDownloads.get(i); |
| final String uri = download.getMediaUri(); |
| if (mediaUri.equals(uri)) { |
| // Delete the file associated with the download |
| final String filename = download.getFilename(); |
| new File(filename).delete(); |
| |
| // Remove the download from the list |
| mDownloads.remove(i); |
| mClean = false; |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @return The list of downloads |
| */ |
| public List<Download> getDownloads() { |
| return mDownloads; |
| } |
| |
| /** |
| * Load metadata from file |
| * |
| * @param videoEditor The video editor |
| * @param projectPath The project path |
| * |
| * @return A new instance of the VideoEditorProject |
| */ |
| public static VideoEditorProject fromXml(VideoEditor videoEditor, String projectPath) |
| throws XmlPullParserException, FileNotFoundException, IOException { |
| final File file = new File(projectPath, PROJECT_METADATA_FILENAME); |
| final FileInputStream fis = new FileInputStream(file); |
| final List<Download> downloads = new ArrayList<Download>(); |
| try { |
| // Load the metadata |
| final XmlPullParser parser = Xml.newPullParser(); |
| parser.setInput(fis, "UTF-8"); |
| int eventType = parser.getEventType(); |
| |
| String projectName = null; |
| String themeId = null; |
| Uri exportedMovieUri = null; |
| long lastSaved = 0; |
| long playheadPosMs = 0; |
| long durationMs = 0; |
| int zoomLevel = DEFAULT_ZOOM_LEVEL; |
| while (eventType != XmlPullParser.END_DOCUMENT) { |
| String name = null; |
| switch (eventType) { |
| case XmlPullParser.START_TAG: { |
| name = parser.getName(); |
| if (name.equalsIgnoreCase(TAG_PROJECT)) { |
| projectName = parser.getAttributeValue("", ATTR_NAME); |
| themeId = parser.getAttributeValue("", ATTR_THEME); |
| lastSaved = Long.parseLong(parser.getAttributeValue("", ATTR_SAVED)); |
| playheadPosMs = Long.parseLong(parser.getAttributeValue("", |
| ATTR_PLAYHEAD_POSITION)); |
| durationMs = Long.parseLong(parser.getAttributeValue("", |
| ATTR_DURATION)); |
| zoomLevel = Integer.parseInt(parser.getAttributeValue("", |
| ATTR_ZOOM_LEVEL)); |
| } else if (name.equalsIgnoreCase(TAG_MOVIE)) { |
| exportedMovieUri = Uri.parse(parser.getAttributeValue("", ATTR_URI)); |
| } else if (name.equalsIgnoreCase(TAG_DOWNLOAD)) { |
| downloads.add(new Download(parser.getAttributeValue("", ATTR_URI), |
| parser.getAttributeValue("", ATTR_MIME), |
| parser.getAttributeValue("", ATTR_FILENAME), |
| Long.parseLong(parser.getAttributeValue("", ATTR_TIME)))); |
| } |
| |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| eventType = parser.next(); |
| } |
| |
| return new VideoEditorProject(videoEditor, projectPath, projectName, lastSaved, |
| playheadPosMs, durationMs, zoomLevel, exportedMovieUri, themeId, downloads); |
| } finally { |
| if (fis != null) { |
| fis.close(); |
| } |
| } |
| } |
| |
| /** |
| * Save the content to XML |
| */ |
| public void saveToXml() throws IOException { |
| // Save the project metadata |
| final XmlSerializer serializer = Xml.newSerializer(); |
| final StringWriter writer = new StringWriter(); |
| serializer.setOutput(writer); |
| serializer.startDocument("UTF-8", true); |
| serializer.startTag("", TAG_PROJECT); |
| if (mProjectName != null) { |
| serializer.attribute("", ATTR_NAME, mProjectName); |
| } |
| if (mTheme != null) { |
| serializer.attribute("", ATTR_THEME, mTheme); |
| } |
| |
| serializer.attribute("", ATTR_PLAYHEAD_POSITION, Long.toString(mPlayheadPosMs)); |
| serializer.attribute("", ATTR_DURATION, Long.toString(computeDuration())); |
| serializer.attribute("", ATTR_ZOOM_LEVEL, Integer.toString(mZoomLevel)); |
| |
| mLastSaved = System.currentTimeMillis(); |
| serializer.attribute("", ATTR_SAVED, Long.toString(mLastSaved)); |
| if (mExportedMovieUri != null) { |
| serializer.startTag("", TAG_MOVIE); |
| serializer.attribute("", ATTR_URI, mExportedMovieUri.toString()); |
| serializer.endTag("", TAG_MOVIE); |
| } |
| |
| for (Download download : mDownloads) { |
| serializer.startTag("", TAG_DOWNLOAD); |
| serializer.attribute("", ATTR_URI, download.getMediaUri()); |
| serializer.attribute("", ATTR_MIME, download.getMimeType()); |
| serializer.attribute("", ATTR_FILENAME, download.getFilename()); |
| serializer.attribute("", ATTR_TIME, Long.toString(download.getTime())); |
| serializer.endTag("", TAG_DOWNLOAD); |
| } |
| serializer.endTag("", TAG_PROJECT); |
| serializer.endDocument(); |
| |
| // Save the metadata XML file |
| final FileOutputStream out = new FileOutputStream(new File(mVideoEditor.getPath(), |
| PROJECT_METADATA_FILENAME)); |
| out.write(writer.toString().getBytes("UTF-8")); |
| out.flush(); |
| out.close(); |
| } |
| } |