/* * Copyright (C) 2014 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.google.android.exoplayer.upstream.cache; import com.google.android.exoplayer.C; import com.google.android.exoplayer.upstream.DataSink; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.util.Assertions; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * Writes data into a cache. */ public class CacheDataSink implements DataSink { private final Cache cache; private final long maxCacheFileSize; private DataSpec dataSpec; private File file; private FileOutputStream outputStream; private long outputStreamBytesWritten; private long dataSpecBytesWritten; /** * Thrown when IOException is encountered when writing data into sink. */ public static class CacheDataSinkException extends IOException { public CacheDataSinkException(IOException cause) { super(cause); } } /** * @param cache The cache into which data should be written. * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into * multiple cache files. */ public CacheDataSink(Cache cache, long maxCacheFileSize) { this.cache = Assertions.checkNotNull(cache); this.maxCacheFileSize = maxCacheFileSize; } @Override public DataSink open(DataSpec dataSpec) throws CacheDataSinkException { // TODO: Support caching for unbounded requests. See TODO in {@link CacheDataSource} for // more details. Assertions.checkState(dataSpec.length != C.LENGTH_UNBOUNDED); try { this.dataSpec = dataSpec; dataSpecBytesWritten = 0; openNextOutputStream(); return this; } catch (FileNotFoundException e) { throw new CacheDataSinkException(e); } } @Override public void write(byte[] buffer, int offset, int length) throws CacheDataSinkException { try { int bytesWritten = 0; while (bytesWritten < length) { if (outputStreamBytesWritten == maxCacheFileSize) { closeCurrentOutputStream(); openNextOutputStream(); } int bytesToWrite = (int) Math.min(length - bytesWritten, maxCacheFileSize - outputStreamBytesWritten); outputStream.write(buffer, offset + bytesWritten, bytesToWrite); bytesWritten += bytesToWrite; outputStreamBytesWritten += bytesToWrite; dataSpecBytesWritten += bytesToWrite; } } catch (IOException e) { throw new CacheDataSinkException(e); } } @Override public void close() throws CacheDataSinkException { try { closeCurrentOutputStream(); } catch (IOException e) { throw new CacheDataSinkException(e); } } private void openNextOutputStream() throws FileNotFoundException { file = cache.startFile(dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten, Math.min(dataSpec.length - dataSpecBytesWritten, maxCacheFileSize)); outputStream = new FileOutputStream(file); outputStreamBytesWritten = 0; } private void closeCurrentOutputStream() throws IOException { if (outputStream != null) { outputStream.flush(); outputStream.close(); outputStream = null; cache.commitFile(file); file = null; } } }