Handle case where length is unset in FileDescriptorDataSource
- Modified the logic of `open()` and `read()` methods to handle scenarios where length is unset for the `FileDescriptor` provided. - Added unit test and contract test to handle this case. Also used `getDeclaredLength()` instead of `getLength()` to set the length of `AssetFileDescriptor` in unit tests and contract tests. PiperOrigin-RevId: 657551343
This commit is contained in:
parent
8360e44e07
commit
004b9d69fd
@ -56,6 +56,20 @@ public final class FileDescriptorDataSourceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadViaFileDescriptorWithUnsetLength() throws Exception {
|
||||
File file = tempFolder.newFile();
|
||||
Files.write(Paths.get(file.getAbsolutePath()), DATA);
|
||||
|
||||
try (FileInputStream inputStream = new FileInputStream(file)) {
|
||||
DataSource dataSource =
|
||||
new FileDescriptorDataSource(inputStream.getFD(), /* offset= */ 0, C.LENGTH_UNSET);
|
||||
|
||||
TestUtil.assertDataSourceContent(
|
||||
dataSource, new DataSpec(Uri.EMPTY), DATA, /* expectKnownLength= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadViaFileDescriptorWithOffset() throws Exception {
|
||||
File file = tempFolder.newFile();
|
||||
@ -78,7 +92,7 @@ public final class FileDescriptorDataSourceTest {
|
||||
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH)) {
|
||||
DataSource dataSource =
|
||||
new FileDescriptorDataSource(
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||
byte[] expectedData =
|
||||
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), ASSET_PATH);
|
||||
|
||||
@ -93,7 +107,7 @@ public final class FileDescriptorDataSourceTest {
|
||||
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH)) {
|
||||
DataSource dataSource =
|
||||
new FileDescriptorDataSource(
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||
DataSpec dataSpec = new DataSpec(Uri.EMPTY, /* position= */ 100, C.LENGTH_UNSET);
|
||||
byte[] data = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), ASSET_PATH);
|
||||
byte[] expectedData = Arrays.copyOfRange(data, /* position= */ 100, data.length);
|
||||
@ -110,11 +124,11 @@ public final class FileDescriptorDataSourceTest {
|
||||
DataSpec dataSpec = new DataSpec(Uri.EMPTY, /* position= */ 100, C.LENGTH_UNSET);
|
||||
DataSource dataSource1 =
|
||||
new FileDescriptorDataSource(
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||
dataSource1.open(dataSpec);
|
||||
DataSource dataSource2 =
|
||||
new FileDescriptorDataSource(
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||
|
||||
// Opening a data source with the same file descriptor is expected to fail.
|
||||
assertThrows(DataSourceException.class, () -> dataSource2.open(dataSpec));
|
||||
|
@ -41,7 +41,7 @@ public class FileDescriptorDataSourceUsingAssetFileDescriptorContractTest
|
||||
AssetFileDescriptor afd =
|
||||
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH);
|
||||
return new FileDescriptorDataSource(
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.datasource;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.test.utils.DataSourceContractTest;
|
||||
import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@ -26,6 +27,7 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
@ -54,15 +56,17 @@ public class FileDescriptorDataSourceUsingFileDescriptorContractTest
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataSource createDataSource() throws Exception {
|
||||
protected List<DataSource> createDataSources() throws Exception {
|
||||
File file = tempFolder.newFile();
|
||||
Files.write(Paths.get(file.getAbsolutePath()), DATA);
|
||||
inputStream = new FileInputStream(file);
|
||||
return new FileDescriptorDataSource(inputStream.getFD(), /* offset= */ 0, DATA.length);
|
||||
return ImmutableList.of(
|
||||
new FileDescriptorDataSource(inputStream.getFD(), /* offset= */ 0, DATA.length),
|
||||
new FileDescriptorDataSource(inputStream.getFD(), /* offset= */ 0, C.LENGTH_UNSET));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableList<TestResource> getTestResources() throws Exception {
|
||||
protected ImmutableList<TestResource> getTestResources() {
|
||||
return ImmutableList.of(
|
||||
new TestResource.Builder()
|
||||
.setName("simple")
|
||||
|
@ -28,11 +28,11 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.io.EOFException;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -58,7 +58,7 @@ public class FileDescriptorDataSource extends BaseDataSource {
|
||||
private final long length;
|
||||
|
||||
@Nullable private Uri uri;
|
||||
@Nullable private InputStream inputStream;
|
||||
@Nullable private FileInputStream inputStream;
|
||||
private long bytesRemaining;
|
||||
private boolean opened;
|
||||
|
||||
@ -78,28 +78,57 @@ public class FileDescriptorDataSource extends BaseDataSource {
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws DataSourceException {
|
||||
if (!inUseFileDescriptors.add(fileDescriptor)) {
|
||||
try {
|
||||
uri = dataSpec.uri;
|
||||
transferInitializing(dataSpec);
|
||||
|
||||
if (!inUseFileDescriptors.add(fileDescriptor)) {
|
||||
throw new DataSourceException(
|
||||
new IllegalStateException("Attempted to re-use an already in-use file descriptor"),
|
||||
PlaybackException.ERROR_CODE_INVALID_STATE);
|
||||
}
|
||||
|
||||
if (length != C.LENGTH_UNSET && dataSpec.position > length) {
|
||||
throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
seekFileDescriptor(fileDescriptor, offset + dataSpec.position);
|
||||
inputStream = new FileInputStream(fileDescriptor);
|
||||
|
||||
if (length == C.LENGTH_UNSET) {
|
||||
// The asset must extend to the end of the file. We can try and resolve the length with
|
||||
// FileInputStream.getChannel().size().
|
||||
FileChannel channel = inputStream.getChannel();
|
||||
long channelSize = channel.size();
|
||||
if (channelSize == 0) {
|
||||
bytesRemaining = C.LENGTH_UNSET;
|
||||
} else {
|
||||
bytesRemaining = channelSize - channel.position();
|
||||
if (bytesRemaining < 0) {
|
||||
// The seek above was successful, but the new position is beyond the end of the file.
|
||||
throw new DataSourceException(
|
||||
PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bytesRemaining = length - dataSpec.position;
|
||||
if (bytesRemaining < 0) {
|
||||
throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
|
||||
}
|
||||
}
|
||||
} catch (DataSourceException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
throw new DataSourceException(
|
||||
new IllegalStateException("Attempted to re-use an already in-use file descriptor"),
|
||||
PlaybackException.ERROR_CODE_INVALID_STATE);
|
||||
e,
|
||||
e instanceof FileNotFoundException
|
||||
? PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND
|
||||
: PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
|
||||
}
|
||||
|
||||
if (dataSpec.position > length) {
|
||||
throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
|
||||
}
|
||||
|
||||
uri = dataSpec.uri;
|
||||
transferInitializing(dataSpec);
|
||||
seekFileDescriptor(fileDescriptor, offset + dataSpec.position);
|
||||
inputStream = new FileInputStream(fileDescriptor);
|
||||
|
||||
if (dataSpec.length != C.LENGTH_UNSET) {
|
||||
bytesRemaining =
|
||||
length != C.LENGTH_UNSET
|
||||
? min(dataSpec.length, length - dataSpec.position)
|
||||
: dataSpec.length;
|
||||
} else {
|
||||
bytesRemaining = length != C.LENGTH_UNSET ? length - dataSpec.position : C.LENGTH_UNSET;
|
||||
bytesRemaining == C.LENGTH_UNSET ? dataSpec.length : min(bytesRemaining, dataSpec.length);
|
||||
}
|
||||
|
||||
opened = true;
|
||||
@ -123,15 +152,6 @@ public class FileDescriptorDataSource extends BaseDataSource {
|
||||
throw new DataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
|
||||
}
|
||||
if (bytesRead == -1) {
|
||||
if (bytesRemaining != C.LENGTH_UNSET) {
|
||||
throw new DataSourceException(
|
||||
new EOFException(
|
||||
String.format(
|
||||
"Attempted to read %d bytes starting at position %d, but reached end of the"
|
||||
+ " file before reading sufficient data.",
|
||||
this.length, this.offset)),
|
||||
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
|
||||
}
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
if (bytesRemaining != C.LENGTH_UNSET) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user