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
|
@Test
|
||||||
public void testReadViaFileDescriptorWithOffset() throws Exception {
|
public void testReadViaFileDescriptorWithOffset() throws Exception {
|
||||||
File file = tempFolder.newFile();
|
File file = tempFolder.newFile();
|
||||||
@ -78,7 +92,7 @@ public final class FileDescriptorDataSourceTest {
|
|||||||
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH)) {
|
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH)) {
|
||||||
DataSource dataSource =
|
DataSource dataSource =
|
||||||
new FileDescriptorDataSource(
|
new FileDescriptorDataSource(
|
||||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||||
byte[] expectedData =
|
byte[] expectedData =
|
||||||
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), ASSET_PATH);
|
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), ASSET_PATH);
|
||||||
|
|
||||||
@ -93,7 +107,7 @@ public final class FileDescriptorDataSourceTest {
|
|||||||
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH)) {
|
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH)) {
|
||||||
DataSource dataSource =
|
DataSource dataSource =
|
||||||
new FileDescriptorDataSource(
|
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);
|
DataSpec dataSpec = new DataSpec(Uri.EMPTY, /* position= */ 100, C.LENGTH_UNSET);
|
||||||
byte[] data = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), ASSET_PATH);
|
byte[] data = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), ASSET_PATH);
|
||||||
byte[] expectedData = Arrays.copyOfRange(data, /* position= */ 100, data.length);
|
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);
|
DataSpec dataSpec = new DataSpec(Uri.EMPTY, /* position= */ 100, C.LENGTH_UNSET);
|
||||||
DataSource dataSource1 =
|
DataSource dataSource1 =
|
||||||
new FileDescriptorDataSource(
|
new FileDescriptorDataSource(
|
||||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||||
dataSource1.open(dataSpec);
|
dataSource1.open(dataSpec);
|
||||||
DataSource dataSource2 =
|
DataSource dataSource2 =
|
||||||
new FileDescriptorDataSource(
|
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.
|
// Opening a data source with the same file descriptor is expected to fail.
|
||||||
assertThrows(DataSourceException.class, () -> dataSource2.open(dataSpec));
|
assertThrows(DataSourceException.class, () -> dataSource2.open(dataSpec));
|
||||||
|
@ -41,7 +41,7 @@ public class FileDescriptorDataSourceUsingAssetFileDescriptorContractTest
|
|||||||
AssetFileDescriptor afd =
|
AssetFileDescriptor afd =
|
||||||
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH);
|
ApplicationProvider.getApplicationContext().getAssets().openFd(ASSET_PATH);
|
||||||
return new FileDescriptorDataSource(
|
return new FileDescriptorDataSource(
|
||||||
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package androidx.media3.datasource;
|
package androidx.media3.datasource;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.test.utils.DataSourceContractTest;
|
import androidx.media3.test.utils.DataSourceContractTest;
|
||||||
import androidx.media3.test.utils.TestUtil;
|
import androidx.media3.test.utils.TestUtil;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -26,6 +27,7 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@ -54,15 +56,17 @@ public class FileDescriptorDataSourceUsingFileDescriptorContractTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DataSource createDataSource() throws Exception {
|
protected List<DataSource> createDataSources() throws Exception {
|
||||||
File file = tempFolder.newFile();
|
File file = tempFolder.newFile();
|
||||||
Files.write(Paths.get(file.getAbsolutePath()), DATA);
|
Files.write(Paths.get(file.getAbsolutePath()), DATA);
|
||||||
inputStream = new FileInputStream(file);
|
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
|
@Override
|
||||||
protected ImmutableList<TestResource> getTestResources() throws Exception {
|
protected ImmutableList<TestResource> getTestResources() {
|
||||||
return ImmutableList.of(
|
return ImmutableList.of(
|
||||||
new TestResource.Builder()
|
new TestResource.Builder()
|
||||||
.setName("simple")
|
.setName("simple")
|
||||||
|
@ -28,11 +28,11 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +58,7 @@ public class FileDescriptorDataSource extends BaseDataSource {
|
|||||||
private final long length;
|
private final long length;
|
||||||
|
|
||||||
@Nullable private Uri uri;
|
@Nullable private Uri uri;
|
||||||
@Nullable private InputStream inputStream;
|
@Nullable private FileInputStream inputStream;
|
||||||
private long bytesRemaining;
|
private long bytesRemaining;
|
||||||
private boolean opened;
|
private boolean opened;
|
||||||
|
|
||||||
@ -78,28 +78,57 @@ public class FileDescriptorDataSource extends BaseDataSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long open(DataSpec dataSpec) throws DataSourceException {
|
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(
|
throw new DataSourceException(
|
||||||
new IllegalStateException("Attempted to re-use an already in-use file descriptor"),
|
e,
|
||||||
PlaybackException.ERROR_CODE_INVALID_STATE);
|
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) {
|
if (dataSpec.length != C.LENGTH_UNSET) {
|
||||||
bytesRemaining =
|
bytesRemaining =
|
||||||
length != C.LENGTH_UNSET
|
bytesRemaining == C.LENGTH_UNSET ? dataSpec.length : min(bytesRemaining, dataSpec.length);
|
||||||
? min(dataSpec.length, length - dataSpec.position)
|
|
||||||
: dataSpec.length;
|
|
||||||
} else {
|
|
||||||
bytesRemaining = length != C.LENGTH_UNSET ? length - dataSpec.position : C.LENGTH_UNSET;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opened = true;
|
opened = true;
|
||||||
@ -123,15 +152,6 @@ public class FileDescriptorDataSource extends BaseDataSource {
|
|||||||
throw new DataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
|
throw new DataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
|
||||||
}
|
}
|
||||||
if (bytesRead == -1) {
|
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;
|
return C.RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
if (bytesRemaining != C.LENGTH_UNSET) {
|
if (bytesRemaining != C.LENGTH_UNSET) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user