শেয়ার্ড স্টোরেজ থেকে মিডিয়া ফাইল অ্যাক্সেস করুন

ব্যবহারকারীদের আরও উন্নত অভিজ্ঞতা দেওয়ার জন্য, অনেক অ্যাপ তাদেরকে এক্সটার্নাল স্টোরেজ ভলিউমে থাকা মিডিয়াতে অবদান রাখতে এবং তা অ্যাক্সেস করার সুযোগ দেয়। এই ফ্রেমওয়ার্কটি মিডিয়া কালেকশনগুলোর জন্য একটি অপ্টিমাইজড ইনডেক্স প্রদান করে, যাকে মিডিয়া স্টোর বলা হয়, যা ব্যবহারকারীদের এই মিডিয়া ফাইলগুলো আরও সহজে পুনরুদ্ধার এবং আপডেট করতে দেয়। আপনার অ্যাপ আনইনস্টল করার পরেও, এই ফাইলগুলো ব্যবহারকারীর ডিভাইসে থেকে যায়।

ফটো পিকার

মিডিয়া স্টোর ব্যবহারের বিকল্প হিসেবে, অ্যান্ড্রয়েড ফটো পিকার টুল ব্যবহারকারীদের জন্য একটি নিরাপদ ও বিল্ট-ইন উপায় প্রদান করে, যার মাধ্যমে তারা আপনার অ্যাপকে তাদের সম্পূর্ণ মিডিয়া লাইব্রেরিতে অ্যাক্সেস দেওয়ার প্রয়োজন ছাড়াই মিডিয়া ফাইল নির্বাচন করতে পারেন। এটি শুধুমাত্র সমর্থিত ডিভাইসগুলিতেই উপলব্ধ। আরও তথ্যের জন্য, ফটো পিকার গাইডটি দেখুন।

মিডিয়া স্টোর

মিডিয়া স্টোর অ্যাবস্ট্রাকশনের সাথে ইন্টারঅ্যাক্ট করতে, আপনার অ্যাপের কনটেক্সট থেকে প্রাপ্ত একটি ContentResolver অবজেক্ট ব্যবহার করুন:

কোটলিন

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

জাভা

String[] projection = new String[] {
        media-database-columns-to-retrieve
};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {
        values-of-placeholder-variables
};
String sortOrder = sql-order-by-clause;

Cursor cursor = getApplicationContext().getContentResolver().query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
);

while (cursor.moveToNext()) {
    // Use an ID column from the projection to get
    // a URI representing the media item itself.
}

সিস্টেমটি স্বয়ংক্রিয়ভাবে একটি এক্সটার্নাল স্টোরেজ ভলিউম স্ক্যান করে এবং মিডিয়া ফাইলগুলিকে নিম্নলিখিত সুনির্দিষ্ট কালেকশনগুলিতে যুক্ত করে:

  • DCIM/ এবং Pictures/ ডিরেক্টরিতে সংরক্ষিত ছবি, যার মধ্যে ফটোগ্রাফ এবং স্ক্রিনশট অন্তর্ভুক্ত। সিস্টেম এই ফাইলগুলোকে MediaStore.Images টেবিলে যুক্ত করে।
  • ভিডিওগুলো DCIM/ , Movies/ এবং Pictures/ ডিরেক্টরিতে সংরক্ষিত থাকে। সিস্টেম এই ফাইলগুলোকে MediaStore.Video টেবিলে যুক্ত করে।
  • অডিও ফাইলগুলো Alarms/ , Audiobooks/ , Music/ , Notifications/ , Podcasts/ , এবং Ringtones/ ডিরেক্টরিতে সংরক্ষিত থাকে। এছাড়াও, সিস্টেমটি Music/ বা Movies/ ডিরেক্টরিতে থাকা অডিও প্লেলিস্ট এবং Recordings/ ডিরেক্টরিতে থাকা ভয়েস রেকর্ডিংগুলোও শনাক্ত করে। সিস্টেম এই ফাইলগুলোকে MediaStore.Audio টেবিলে যুক্ত করে। Recordings/ ডিরেক্টরিটি Android 11 (API লেভেল 30) এবং এর নিচের সংস্করণগুলোতে উপলব্ধ নয়।
  • ডাউনলোড করা ফাইলগুলো Download/ ডিরেক্টরিতে সংরক্ষিত থাকে। Android 10 (API লেভেল 29) এবং তার উপরের সংস্করণে চালিত ডিভাইসগুলোতে এই ফাইলগুলো MediaStore.Downloads টেবিলে সংরক্ষিত হয়। Android 9 (API লেভেল 28) এবং তার নিচের সংস্করণগুলোতে এই টেবিলটি পাওয়া যায় না।

মিডিয়া স্টোরে MediaStore.Files নামে একটি কালেকশনও রয়েছে। এর বিষয়বস্তু নির্ভর করে আপনার অ্যাপ স্কোপড স্টোরেজ ব্যবহার করে কিনা তার উপর, যা অ্যান্ড্রয়েড ১০ বা তার উচ্চতর সংস্করণকে লক্ষ্য করে তৈরি অ্যাপগুলোতে উপলব্ধ।

  • স্কোপড স্টোরেজ সক্রিয় করা থাকলে, কালেকশনটি শুধুমাত্র সেইসব ফটো, ভিডিও এবং অডিও ফাইল দেখায় যা আপনার অ্যাপ তৈরি করেছে। বেশিরভাগ ডেভেলপারের অন্য অ্যাপের মিডিয়া ফাইল দেখার জন্য MediaStore.Files ব্যবহার করার প্রয়োজন হয় না, কিন্তু যদি আপনার এমন করার কোনো নির্দিষ্ট প্রয়োজন থাকে, তবে আপনি READ_EXTERNAL_STORAGE পারমিশনটি ঘোষণা করতে পারেন। তবে, আমরা সুপারিশ করি যে, আপনার অ্যাপ তৈরি করেনি এমন ফাইল খোলার জন্য আপনি MediaStore API ব্যবহার করুন।
  • স্কোপড স্টোরেজ অনুপলব্ধ বা ব্যবহৃত না হলে, সংগ্রহটিতে সব ধরনের মিডিয়া ফাইল দেখানো হয়।

প্রয়োজনীয় অনুমতির জন্য অনুরোধ করুন

মিডিয়া ফাইলগুলিতে কোনো অপারেশন চালানোর আগে, নিশ্চিত করুন যে আপনার অ্যাপটি এই ফাইলগুলি অ্যাক্সেস করার জন্য প্রয়োজনীয় অনুমতিগুলি ঘোষণা করেছে। তবে, এমন কোনো অনুমতি ঘোষণা না করার বিষয়ে সতর্ক থাকুন যা আপনার অ্যাপের প্রয়োজন নেই বা যা এটি ব্যবহার করে না।

স্টোরেজ অনুমতি

আপনার অ্যাপটি শুধুমাত্র তার নিজের মিডিয়া ফাইল অ্যাক্সেস করে, নাকি অন্য অ্যাপের তৈরি করা ফাইলও অ্যাক্সেস করে, তার উপর নির্ভর করে স্টোরেজ অ্যাক্সেস করার জন্য অনুমতির প্রয়োজন আছে কিনা।

আপনার নিজের মিডিয়া ফাইল অ্যাক্সেস করুন

অ্যান্ড্রয়েড ১০ বা তার উচ্চতর সংস্করণে চালিত ডিভাইসগুলিতে, আপনার অ্যাপের মালিকানাধীন মিডিয়া ফাইলগুলি অ্যাক্সেস এবং পরিবর্তন করার জন্য স্টোরেজ-সম্পর্কিত অনুমতির প্রয়োজন হয় না, যার মধ্যে MediaStore.Downloads কালেকশনের ফাইলগুলিও অন্তর্ভুক্ত। উদাহরণস্বরূপ, আপনি যদি একটি ক্যামেরা অ্যাপ তৈরি করেন, তবে এর তোলা ছবিগুলি অ্যাক্সেস করার জন্য স্টোরেজ-সম্পর্কিত অনুমতির অনুরোধ করার প্রয়োজন নেই, কারণ যে ছবিগুলি আপনি মিডিয়া স্টোরে লিখছেন, সেগুলির মালিক আপনার অ্যাপই।

অন্যান্য অ্যাপের মিডিয়া ফাইল অ্যাক্সেস করুন

অন্যান্য অ্যাপের তৈরি করা মিডিয়া ফাইল অ্যাক্সেস করতে হলে, আপনাকে অবশ্যই উপযুক্ত স্টোরেজ-সংক্রান্ত অনুমতিগুলো ঘোষণা করতে হবে এবং ফাইলগুলো নিম্নলিখিত মিডিয়া কালেকশনগুলোর মধ্যে কোনো একটিতে থাকতে হবে:

যতক্ষণ কোনো ফাইল MediaStore.Images , MediaStore.Video , বা MediaStore.Audio কোয়েরি থেকে দেখা যায়, ততক্ষণ সেটি MediaStore.Files কোয়েরি ব্যবহার করেও দেখা যায়।

নিম্নলিখিত কোড স্নিপেটটি দেখায় কিভাবে উপযুক্ত স্টোরেজ পারমিশন ঘোষণা করতে হয়:

<!-- Required only if your app needs to access images or photos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- Required only if your app needs to access videos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- Required only if your app needs to access audio files
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="29" />

পুরোনো ডিভাইসগুলিতে চলমান অ্যাপগুলির জন্য অতিরিক্ত অনুমতির প্রয়োজন।

আপনার অ্যাপটি যদি অ্যান্ড্রয়েড ৯ বা তার নিম্নতর সংস্করণের কোনো ডিভাইসে ব্যবহৃত হয়, অথবা যদি আপনার অ্যাপটি সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট-আউট করে থাকে, তাহলে যেকোনো মিডিয়া ফাইল অ্যাক্সেস করার জন্য আপনাকে অবশ্যই READ_EXTERNAL_STORAGE পারমিশনের জন্য অনুরোধ করতে হবে। আপনি যদি মিডিয়া ফাইলগুলো পরিবর্তন করতে চান, তাহলে আপনাকে অবশ্যই WRITE_EXTERNAL_STORAGE পারমিশনের জন্যও অনুরোধ করতে হবে।

অন্যান্য অ্যাপের ডাউনলোড অ্যাক্সেস করার জন্য স্টোরেজ অ্যাক্সেস ফ্রেমওয়ার্ক প্রয়োজন।

আপনার অ্যাপ যদি MediaStore.Downloads কালেকশনের মধ্যে থাকা এমন কোনো ফাইল অ্যাক্সেস করতে চায় যা আপনার অ্যাপ তৈরি করেনি, তাহলে আপনাকে অবশ্যই স্টোরেজ অ্যাক্সেস ফ্রেমওয়ার্ক ব্যবহার করতে হবে। এই ফ্রেমওয়ার্কটি কীভাবে ব্যবহার করতে হয় সে সম্পর্কে আরও জানতে, “শেয়ার্ড স্টোরেজ থেকে ডকুমেন্ট এবং অন্যান্য ফাইল অ্যাক্সেস করুন” দেখুন।

মিডিয়া অবস্থানের অনুমতি

আপনার অ্যাপটি যদি অ্যান্ড্রয়েড ১০ (এপিআই লেভেল ২৯) বা তার উচ্চতর সংস্করণকে টার্গেট করে এবং ফটোগুলো থেকে অপরিবর্তিত EXIF ​​মেটাডেটা পুনরুদ্ধার করার প্রয়োজন হয়, তাহলে আপনাকে আপনার অ্যাপের ম্যানিফেস্টে ACCESS_MEDIA_LOCATION পারমিশনটি ঘোষণা করতে হবে এবং তারপর রানটাইমে এই পারমিশনটির জন্য অনুরোধ করতে হবে।

মিডিয়া স্টোরের আপডেটগুলি পরীক্ষা করুন

মিডিয়া ফাইলগুলি আরও নির্ভরযোগ্যভাবে অ্যাক্সেস করার জন্য, বিশেষ করে যদি আপনার অ্যাপ মিডিয়া স্টোর থেকে URI বা ডেটা ক্যাশ করে রাখে, তাহলে শেষবার আপনার মিডিয়া ডেটা সিঙ্ক করার সময়ের তুলনায় মিডিয়া স্টোরের ভার্সন পরিবর্তিত হয়েছে কিনা তা পরীক্ষা করুন। এই আপডেটগুলি পরীক্ষা করার জন্য, getVersion() কল করুন। ফেরত আসা ভার্সনটি একটি অনন্য স্ট্রিং, যা মিডিয়া স্টোরে বড় ধরনের কোনো পরিবর্তন হলেই পরিবর্তিত হয়। যদি ফেরত আসা ভার্সনটি শেষ সিঙ্ক করা ভার্সন থেকে ভিন্ন হয়, তাহলে আপনার অ্যাপের মিডিয়া ক্যাশ পুনরায় স্ক্যান এবং সিঙ্ক করুন।

অ্যাপ প্রসেস চালু হওয়ার সময় এই চেকটি সম্পন্ন করুন। প্রতিবার মিডিয়া স্টোর থেকে ডেটা নেওয়ার সময় ভার্সন চেক করার কোনো প্রয়োজন নেই।

ভার্সন নম্বর সংক্রান্ত বাস্তবায়নের কোনো বিবরণ অনুমান করবেন না।

একটি মিডিয়া সংগ্রহ অনুসন্ধান করুন

নির্দিষ্ট কিছু শর্ত পূরণ করে এমন মিডিয়া খুঁজে বের করতে, যেমন যার সময়কাল ৫ মিনিট বা তার বেশি, নিচের কোড স্নিপেটে দেখানো SQL-এর মতো একটি সিলেকশন স্টেটমেন্ট ব্যবহার করুন:

কোটলিন

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
data class Video(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val videoList = mutableListOf<Video>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Video.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
)

// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)

// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    val durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val duration = cursor.getInt(durationColumn)
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList += Video(contentUri, name, duration, size)
    }
}

জাভা

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
class Video {
    private final Uri uri;
    private final String name;
    private final int duration;
    private final int size;

    public Video(Uri uri, String name, int duration, int size) {
        this.uri = uri;
        this.name = name;
        this.duration = duration;
        this.size = size;
    }
}
List<Video> videoList = new ArrayList<Video>();

Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
    collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}

String[] projection = new String[] {
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
        " >= ?";
String[] selectionArgs = new String[] {
    String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC";

try (Cursor cursor = getApplicationContext().getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    // Cache column indices.
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    int nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
    int durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
    int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        long id = cursor.getLong(idColumn);
        String name = cursor.getString(nameColumn);
        int duration = cursor.getInt(durationColumn);
        int size = cursor.getInt(sizeColumn);

        Uri contentUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList.add(new Video(contentUri, name, duration, size));
    }
}

আপনার অ্যাপে এই ধরনের কোয়েরি চালানোর সময় নিম্নলিখিত বিষয়গুলো মনে রাখবেন:

  • একটি ওয়ার্কার থ্রেডে query() মেথডটি কল করুন।
  • কলাম ইনডেক্সগুলো ক্যাশ করে রাখুন, যাতে কোয়েরির ফলাফল থেকে প্রতিবার একটি সারি প্রসেস করার সময় আপনাকে getColumnIndexOrThrow() কল করতে না হয়।
  • এই উদাহরণে দেখানো অনুযায়ী কন্টেন্ট URI-এর সাথে ID-টি যুক্ত করুন।
  • অ্যান্ড্রয়েড ১০ বা তার পরবর্তী সংস্করণ চালিত ডিভাইসগুলোতে MediaStore API-তে সংজ্ঞায়িত কলামের নাম প্রয়োজন হয়। যদি আপনার অ্যাপের মধ্যে থাকা কোনো নির্ভরশীল লাইব্রেরি এমন কোনো কলামের নাম আশা করে যা API-তে সংজ্ঞায়িত নয়, যেমন "MimeType" , তাহলে আপনার অ্যাপের প্রসেসে কলামের নামটি ডায়নামিকভাবে অনুবাদ করতে CursorWrapper ব্যবহার করুন।

ফাইল থাম্বনেইল লোড করুন

যদি আপনার অ্যাপ একাধিক মিডিয়া ফাইল দেখায় এবং ব্যবহারকারীকে সেগুলোর মধ্যে থেকে একটি বেছে নিতে বলে, তাহলে সরাসরি ফাইলগুলো লোড করার পরিবর্তে সেগুলোর প্রিভিউ ভার্সন—বা থাম্বনেইল —লোড করা বেশি কার্যকর।

কোনো নির্দিষ্ট মিডিয়া ফাইলের থাম্বনেইল লোড করতে, loadThumbnail() ব্যবহার করুন এবং আপনি যে আকারের থাম্বনেইল লোড করতে চান তা পাস করুন, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

কোটলিন

// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

জাভা

// Load thumbnail of a specific media item.
Bitmap thumbnail =
        getApplicationContext().getContentResolver().loadThumbnail(
        content-uri, new Size(640, 480), null);

একটি মিডিয়া ফাইল খুলুন

একটি মিডিয়া ফাইল খোলার জন্য আপনি যে নির্দিষ্ট লজিক ব্যবহার করেন, তা নির্ভর করে মিডিয়া কন্টেন্টটি ফাইল ডেসক্রিপ্টর, ফাইল স্ট্রিম, নাকি সরাসরি ফাইল পাথ হিসেবে সবচেয়ে ভালোভাবে উপস্থাপন করা যায়, তার ওপর।

ফাইল বর্ণনাকারী

ফাইল ডেসক্রিপ্টর ব্যবহার করে একটি মিডিয়া ফাইল খুলতে, নিম্নলিখিত কোড স্নিপেটে দেখানো লজিকের অনুরূপ লজিক ব্যবহার করুন:

কোটলিন

// Open a specific media item using ParcelFileDescriptor.
val resolver = applicationContext.contentResolver

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd ->
    // Perform operations on "pfd".
}

জাভা

// Open a specific media item using ParcelFileDescriptor.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
String readOnlyMode = "r";
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(content-uri, readOnlyMode)) {
    // Perform operations on "pfd".
} catch (IOException e) {
    e.printStackTrace();
}

ফাইল স্ট্রিম

ফাইল স্ট্রিম ব্যবহার করে একটি মিডিয়া ফাইল খুলতে, নিম্নলিখিত কোড স্নিপেটে দেখানো লজিকের অনুরূপ লজিক ব্যবহার করুন:

কোটলিন

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}

জাভা

// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {
    // Perform operations on "stream".
}

সরাসরি ফাইল পাথ

আপনার অ্যাপকে থার্ড-পার্টি মিডিয়া লাইব্রেরির সাথে আরও সহজে কাজ করতে সাহায্য করার জন্য, অ্যান্ড্রয়েড ১১ (এপিআই লেভেল ৩০) এবং এর পরবর্তী সংস্করণগুলো আপনাকে শেয়ার্ড স্টোরেজ থেকে মিডিয়া ফাইল অ্যাক্সেস করার জন্য MediaStore API ছাড়াও অন্যান্য এপিআই ব্যবহার করার সুযোগ দেয়। এর পরিবর্তে, আপনি নিম্নলিখিত এপিআইগুলোর যেকোনো একটি ব্যবহার করে সরাসরি মিডিয়া ফাইল অ্যাক্সেস করতে পারেন:

  • File এপিআই
  • নেটিভ লাইব্রেরি, যেমন fopen()

আপনার কোনো স্টোরেজ-সংক্রান্ত অনুমতি না থাকলেও, আপনি ফাইল এপিআই ( File API) ব্যবহার করে আপনার অ্যাপ-নির্দিষ্ট ডিরেক্টরির ফাইল এবং আপনার অ্যাপের সাথে সম্পর্কিত মিডিয়া ফাইলগুলো অ্যাক্সেস করতে পারবেন।

আপনার অ্যাপ যদি ফাইল এপিআই File API) ব্যবহার করে কোনো ফাইল অ্যাক্সেস করার চেষ্টা করে এবং সেটির প্রয়োজনীয় অনুমতি না থাকে, তাহলে একটি FileNotFoundException ঘটে।

Android 10 (API লেভেল 29) চালিত কোনো ডিভাইসে শেয়ার্ড স্টোরেজে থাকা অন্যান্য ফাইল অ্যাক্সেস করার জন্য, আমরা আপনাকে আপনার অ্যাপের ম্যানিফেস্ট ফাইলে requestLegacyExternalStorage true সেট করে সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট-আউট করার পরামর্শ দিচ্ছি। Android 10-এ নেটিভ ফাইল মেথড ব্যবহার করে মিডিয়া ফাইল অ্যাক্সেস করার জন্য, আপনাকে অবশ্যই READ_EXTERNAL_STORAGE পারমিশনটিও অনুরোধ করতে হবে।

মিডিয়া কন্টেন্ট অ্যাক্সেস করার সময় বিবেচ্য বিষয়সমূহ

মিডিয়া কন্টেন্ট দেখার সময়, নিম্নলিখিত বিভাগগুলিতে আলোচিত বিষয়গুলি মনে রাখবেন।

ক্যাশ করা ডেটা

আপনার অ্যাপ যদি মিডিয়া স্টোর থেকে URI বা ডেটা ক্যাশ করে রাখে, তবে পর্যায়ক্রমে মিডিয়া স্টোরের আপডেটের জন্য পরীক্ষা করুন । এই পরীক্ষাটি আপনার অ্যাপ-সাইডের ক্যাশ করা ডেটাকে সিস্টেম-সাইডের প্রোভাইডার ডেটার সাথে সিঙ্ক থাকতে সাহায্য করে।

কর্মক্ষমতা

সরাসরি ফাইল পাথ ব্যবহার করে মিডিয়া ফাইলগুলো ক্রমানুসারে রিড করার সময়, এর পারফরম্যান্স MediaStore API-এর পারফরম্যান্সের সমতুল্য হয়।

তবে, সরাসরি ফাইল পাথ ব্যবহার করে মিডিয়া ফাইল থেকে এলোমেলোভাবে ডেটা পড়া ও লেখার সময়, প্রক্রিয়াটি দ্বিগুণ পর্যন্ত ধীর হতে পারে। এই ধরনের পরিস্থিতিতে, আমরা এর পরিবর্তে MediaStore API ব্যবহার করার পরামর্শ দিই।

ডেটা কলাম

যখন আপনি কোনো বিদ্যমান মিডিয়া ফাইল অ্যাক্সেস করেন, তখন আপনার লজিকে DATA কলামের মান ব্যবহার করতে পারেন। এর কারণ হলো, এই মানটিতে একটি বৈধ ফাইল পাথ থাকে। তবে, ফাইলটি সবসময় পাওয়া যাবে এমনটা ধরে নেবেন না। যেকোনো ফাইল-ভিত্তিক I/O ত্রুটি ঘটলে তা সামাল দেওয়ার জন্য প্রস্তুত থাকুন।

অন্যদিকে, একটি মিডিয়া ফাইল তৈরি বা আপডেট করতে DATA কলামের মান ব্যবহার করবেন না। এর পরিবর্তে DISPLAY_NAME এবং RELATIVE_PATH কলামের মান ব্যবহার করুন।

স্টোরেজ ভলিউম

যেসব অ্যাপ অ্যান্ড্রয়েড ১০ বা তার উচ্চতর সংস্করণকে লক্ষ্য করে তৈরি, সেগুলো প্রতিটি এক্সটার্নাল স্টোরেজ ভলিউমের জন্য সিস্টেম কর্তৃক নির্ধারিত অনন্য নামটি অ্যাক্সেস করতে পারে। এই নামকরণ ব্যবস্থাটি আপনাকে দক্ষতার সাথে কন্টেন্ট সাজাতে ও সূচীবদ্ধ করতে সাহায্য করে এবং নতুন মিডিয়া ফাইলগুলো কোথায় সংরক্ষিত হবে, তার ওপর আপনাকে নিয়ন্ত্রণ দেয়।

নিম্নলিখিত খণ্ডগুলো মনে রাখা বিশেষভাবে উপকারী:

  • VOLUME_EXTERNAL ভলিউমটি ডিভাইসের সমস্ত শেয়ার্ড স্টোরেজ ভলিউমের একটি দৃশ্য প্রদান করে। আপনি এই সিন্থেটিক ভলিউমের বিষয়বস্তু পড়তে পারবেন, কিন্তু এর বিষয়বস্তু পরিবর্তন করতে পারবেন না।
  • VOLUME_EXTERNAL_PRIMARY ভলিউমটি ডিভাইসের প্রধান শেয়ার্ড স্টোরেজ ভলিউমকে প্রতিনিধিত্ব করে। আপনি এই ভলিউমের বিষয়বস্তু পড়তে এবং পরিবর্তন করতে পারেন।

আপনি MediaStore.getExternalVolumeNames() কল করে অন্যান্য ভলিউমগুলি খুঁজে পেতে পারেন:

কোটলিন

val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context)
val firstVolumeName = volumeNames.iterator().next()

জাভা

Set<String> volumeNames = MediaStore.getExternalVolumeNames(context);
String firstVolumeName = volumeNames.iterator().next();

যে স্থানে মিডিয়া ধারণ করা হয়েছিল

কিছু ছবি এবং ভিডিওর মেটাডেটাতে অবস্থানের তথ্য থাকে, যা থেকে জানা যায় ছবিটি কোথায় তোলা হয়েছিল বা ভিডিওটি কোথায় রেকর্ড করা হয়েছিল।

আপনার অ্যাপে এই অবস্থানের তথ্য কীভাবে অ্যাক্সেস করবেন, তা নির্ভর করে আপনার একটি ছবি বা ভিডিওর জন্য অবস্থানের তথ্য প্রয়োজন কিনা তার উপর।

ছবি

আপনার অ্যাপ যদি স্কোপড স্টোরেজ ব্যবহার করে, তাহলে সিস্টেম ডিফল্টরূপে অবস্থানের তথ্য গোপন রাখে। এই তথ্য অ্যাক্সেস করতে, নিম্নলিখিত ধাপগুলি সম্পন্ন করুন:

  1. আপনার অ্যাপের ম্যানিফেস্টে ACCESS_MEDIA_LOCATION পারমিশনটির জন্য অনুরোধ করুন।
  2. আপনার MediaStore অবজেক্ট থেকে, ফটোগ্রাফটির URI পাস করে setRequireOriginal() কল করার মাধ্যমে ফটোগ্রাফটির সঠিক বাইটগুলো নিন, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

    কোটলিন

    val photoUri: Uri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex)
    )
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri)?.use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = latLong ?: doubleArrayOf(0.0, 0.0)
        }
    }

    জাভা

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));
    
    final double[] latLong;
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();
    
        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
        // Don't reuse the stream associated with
        // the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }

ভিডিও

একটি ভিডিওর মেটাডেটার মধ্যে থাকা অবস্থানের তথ্য অ্যাক্সেস করতে, নিচের কোড স্নিপেটে দেখানো অনুযায়ী MediaMetadataRetriever ক্লাসটি ব্যবহার করুন। এই ক্লাসটি ব্যবহার করার জন্য আপনার অ্যাপের কোনো অতিরিক্ত অনুমতির অনুরোধ করার প্রয়োজন নেই।

কোটলিন

val retriever = MediaMetadataRetriever()
val context = applicationContext

// Find the videos that are stored on a device by querying the video collection.
val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val videoUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )
        extractVideoLocationInfo(videoUri)
    }
}

private fun extractVideoLocationInfo(videoUri: Uri) {
    try {
        retriever.setDataSource(context, videoUri)
    } catch (e: RuntimeException) {
        Log.e(APP_TAG, "Cannot retrieve video file", e)
    }
    // Metadata uses a standardized format.
    val locationMetadata: String? =
            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
}

জাভা

MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Context context = getApplicationContext();

// Find the videos that are stored on a device by querying the video collection.
try (Cursor cursor = context.getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    while (cursor.moveToNext()) {
        long id = cursor.getLong(idColumn);
        Uri videoUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
        extractVideoLocationInfo(videoUri);
    }
}

private void extractVideoLocationInfo(Uri videoUri) {
    try {
        retriever.setDataSource(context, videoUri);
    } catch (RuntimeException e) {
        Log.e(APP_TAG, "Cannot retrieve video file", e);
    }
    // Metadata uses a standardized format.
    String locationMetadata = retriever.extractMetadata(
            MediaMetadataRetriever.METADATA_KEY_LOCATION);
}

শেয়ারিং

কিছু অ্যাপ ব্যবহারকারীদের একে অপরের সাথে মিডিয়া ফাইল শেয়ার করার সুযোগ দেয়। উদাহরণস্বরূপ, সোশ্যাল মিডিয়া অ্যাপগুলো ব্যবহারকারীদের বন্ধুদের সাথে ছবি ও ভিডিও শেয়ার করতে দেয়।

মিডিয়া ফাইল শেয়ার করতে, কন্টেন্ট প্রোভাইডার তৈরির নির্দেশিকায় সুপারিশ অনুযায়ী একটি content:// URI ব্যবহার করুন।

মিডিয়া ফাইলগুলির অ্যাপ অ্যাট্রিবিউশন

যখন অ্যান্ড্রয়েড ১০ বা তার উচ্চতর সংস্করণকে লক্ষ্য করে তৈরি কোনো অ্যাপের জন্য স্কোপড স্টোরেজ সক্রিয় করা হয়, তখন সিস্টেম প্রতিটি মিডিয়া ফাইলের জন্য একটি অ্যাপ নির্ধারণ করে দেয় । এর মাধ্যমে ঠিক করা হয় যে, আপনার অ্যাপ কোনো স্টোরেজ অনুমতির জন্য অনুরোধ না করা সত্ত্বেও কোন ফাইলগুলো অ্যাক্সেস করতে পারবে। প্রতিটি ফাইলকে শুধুমাত্র একটি অ্যাপের সাথেই যুক্ত করা যায়। সুতরাং, যদি আপনার অ্যাপ এমন কোনো মিডিয়া ফাইল তৈরি করে যা ফটো, ভিডিও বা অডিও ফাইলের মিডিয়া কালেকশনে সংরক্ষিত থাকে, তাহলে আপনার অ্যাপটি সেই ফাইলটি অ্যাক্সেস করতে পারবে।

তবে, যদি ব্যবহারকারী আপনার অ্যাপটি আনইনস্টল করে আবার ইনস্টল করে, তাহলে আপনার অ্যাপ দ্বারা মূলত তৈরি করা ফাইলগুলি অ্যাক্সেস করার জন্য আপনাকে অবশ্যই READ_EXTERNAL_STORAGE জন্য অনুরোধ করতে হবে। এই অনুমতির অনুরোধটি প্রয়োজন, কারণ সিস্টেম ফাইলটিকে নতুন ইনস্টল করা সংস্করণের পরিবর্তে অ্যাপটির পূর্বে ইনস্টল করা সংস্করণের অংশ হিসেবে বিবেচনা করে।

অ্যান্ড্রয়েড ১৬ বা তার উচ্চতর সংস্করণে চালিত ডিভাইসে, এসডিকে ৩৬ বা তার উচ্চতর সংস্করণকে লক্ষ্য করে তৈরি কোনো অ্যাপ যখন ফটো এবং ভিডিওর অনুমতি চায়, তখন যে ব্যবহারকারীরা নির্বাচিত মিডিয়ার অ্যাক্সেস সীমিত করতে চান, তারা ফটো পিকার-এ অ্যাপটির মালিকানাধীন যেকোনো ফটো আগে থেকেই নির্বাচিত অবস্থায় দেখতে পাবেন। ব্যবহারকারীরা এই আগে থেকে নির্বাচিত আইটেমগুলোর যেকোনোটি অনির্বাচিত করতে পারেন, যার ফলে সেই ফটো এবং ভিডিওগুলোতে অ্যাপটির অ্যাক্সেস বাতিল হয়ে যাবে।

একটি আইটেম যোগ করুন

বিদ্যমান কোনো সংগ্রহে একটি মিডিয়া আইটেম যোগ করতে, নিচের মতো কোড ব্যবহার করুন। এই কোড স্নিপেটটি অ্যান্ড্রয়েড ১০ বা তার উচ্চতর সংস্করণে চালিত ডিভাইসগুলিতে VOLUME_EXTERNAL_PRIMARY ভলিউম অ্যাক্সেস করে। এর কারণ হলো, এই ডিভাইসগুলিতে, আপনি শুধুমাত্র তখনই একটি ভলিউমের বিষয়বস্তু পরিবর্তন করতে পারবেন যদি সেটি প্রাইমারি ভলিউম হয়, যেমনটি স্টোরেজ ভলিউম বিভাগে বর্ণনা করা হয়েছে।

কোটলিন

// Add a specific media item.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

// Publish a new song.
val newSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3")
}

// Keep a handle to the new song's URI in case you need to modify it
// later.
val myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails)

জাভা

// Add a specific media item.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

// Publish a new song.
ContentValues newSongDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Song.mp3");

// Keep a handle to the new song's URI in case you need to modify it
// later.
Uri myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails);

মিডিয়া ফাইলগুলির জন্য অপেক্ষারত অবস্থা টগল করুন

আপনার অ্যাপ যদি মিডিয়া ফাইলে লেখার মতো সময়সাপেক্ষ কোনো কাজ করে থাকে, তবে ফাইলটি প্রসেস হওয়ার সময় সেটির ওপর এক্সক্লুসিভ অ্যাক্সেস থাকাটা বেশ সুবিধাজনক। অ্যান্ড্রয়েড ১০ বা তার চেয়ে উন্নত সংস্করণে চালিত ডিভাইসগুলোতে, আপনার অ্যাপ IS_PENDING ফ্ল্যাগের মান ১-এ সেট করার মাধ্যমে এই এক্সক্লুসিভ অ্যাক্সেস পেতে পারে। যতক্ষণ না আপনার অ্যাপ IS_PENDING এর মান পরিবর্তন করে আবার ০ করছে, ততক্ষণ পর্যন্ত শুধুমাত্র আপনার অ্যাপই ফাইলটি দেখতে পারবে।

নিম্নলিখিত কোড স্নিপেটটি পূর্ববর্তী কোড স্নিপেটের উপর ভিত্তি করে তৈরি। এই স্নিপেটটি দেখায় কিভাবে MediaStore.Audio কালেকশনের সাথে সম্পর্কিত ডিরেক্টরিতে একটি দীর্ঘ গান সংরক্ষণ করার সময় IS_PENDING ফ্ল্যাগটি ব্যবহার করতে হয়:

কোটলিন

// Add a media item that other apps don't see until the item is
// fully written to the media store.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

val songDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3")
    put(MediaStore.Audio.Media.IS_PENDING, 1)
}

val songContentUri = resolver.insert(audioCollection, songDetails)

// "w" for write.
resolver.openFileDescriptor(songContentUri, "w", null).use { pfd ->
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)

জাভা

// Add a media item that other apps don't see until the item is
// fully written to the media store.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

ContentValues songDetails = new ContentValues();
songDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Workout Playlist.mp3");
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 1);

Uri songContentUri = resolver
        .insert(audioCollection, songDetails);

// "w" for write.
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(songContentUri, "w", null)) {
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear();
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0);
resolver.update(songContentUri, songDetails, null, null);

ফাইলের অবস্থান সম্পর্কে ইঙ্গিত দিন।

যখন আপনার অ্যাপ অ্যান্ড্রয়েড ১০ চালিত কোনো ডিভাইসে মিডিয়া সংরক্ষণ করে, তখন ডিফল্টরূপে মিডিয়াগুলো সেগুলোর ধরন অনুযায়ী সাজানো হয়। উদাহরণস্বরূপ, ডিফল্টরূপে নতুন ইমেজ ফাইলগুলো Environment.DIRECTORY_PICTURES ডিরেক্টরিতে রাখা হয়, যা MediaStore.Images কালেকশনের সাথে সম্পর্কিত।

আপনার অ্যাপ যদি ফাইল সংরক্ষণের কোনো নির্দিষ্ট স্থান সম্পর্কে অবগত থাকে, যেমন Pictures/MyVacationPictures নামের একটি ফটো অ্যালবাম, তাহলে নতুন লেখা ফাইলগুলো কোথায় সংরক্ষণ করতে হবে সে সম্পর্কে সিস্টেমকে একটি ইঙ্গিত দেওয়ার জন্য আপনি MediaColumns.RELATIVE_PATH সেট করতে পারেন।

একটি আইটেম আপডেট করুন

আপনার অ্যাপের মালিকানাধীন কোনো মিডিয়া ফাইল আপডেট করতে, নিম্নলিখিত কোডের অনুরূপ কোড ব্যবহার করুন:

কোটলিন

// Updates an existing media item.
val mediaId = // MediaStore.Audio.Media._ID of item to update.
val resolver = applicationContext.contentResolver

// When performing a single item update, prefer using the ID.
val selection = "${MediaStore.Audio.Media._ID} = ?"

// By using selection + args you protect against improper escaping of // values.
val selectionArgs = arrayOf(mediaId.toString())

// Update an existing song.
val updatedSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3")
}

// Use the individual song's URI to represent the collection that's
// updated.
val numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs)

জাভা

// Updates an existing media item.
long mediaId = // MediaStore.Audio.Media._ID of item to update.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// When performing a single item update, prefer using the ID.
String selection = MediaStore.Audio.Media._ID + " = ?";

// By using selection + args you protect against improper escaping of
// values. Here, "song" is an in-memory object that caches the song's
// information.
String[] selectionArgs = new String[] { getId().toString() };

// Update an existing song.
ContentValues updatedSongDetails = new ContentValues();
updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Favorite Song.mp3");

// Use the individual song's URI to represent the collection that's
// updated.
int numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs);

যদি স্কোপড স্টোরেজ অনুপলব্ধ বা সক্রিয় না থাকে, তাহলেও পূর্ববর্তী কোড স্নিপেটে দেখানো প্রক্রিয়াটি সেই ফাইলগুলির জন্যও কাজ করে যেগুলির মালিক আপনার অ্যাপ নয়।

নেটিভ কোডে আপডেট করুন

নেটিভ লাইব্রেরি ব্যবহার করে মিডিয়া ফাইল লেখার প্রয়োজন হলে, আপনার জাভা-ভিত্তিক বা কোটলিন-ভিত্তিক কোড থেকে ফাইলটির সংশ্লিষ্ট ফাইল ডেসক্রিপ্টরটি আপনার নেটিভ কোডে পাস করুন।

নিম্নলিখিত কোড স্নিপেটটি দেখায় কিভাবে একটি মিডিয়া অবজেক্টের ফাইল ডেসক্রিপ্টর আপনার অ্যাপের নেটিভ কোডে পাস করতে হয়:

কোটলিন

val contentUri: Uri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode)
val fd = parcelFd?.detachFd()
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.

জাভা

Uri contentUri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);
if (parcelFd != null) {
    int fd = parcelFd.detachFd();
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
}

অন্যান্য অ্যাপের মিডিয়া ফাইল আপডেট করুন

আপনার অ্যাপ যদি স্কোপড স্টোরেজ ব্যবহার করে, তবে এটি সাধারণত এমন কোনো মিডিয়া ফাইল আপডেট করতে পারে না যা অন্য কোনো অ্যাপ মিডিয়া স্টোরে যুক্ত করেছে।

তবে, প্ল্যাটফর্মের থ্রো করা RecoverableSecurityException টি ক্যাচ করার মাধ্যমে আপনি ফাইলটি পরিবর্তন করার জন্য ব্যবহারকারীর সম্মতি নিতে পারেন। এরপর, নিম্নলিখিত কোড স্নিপেটে দেখানো অনুযায়ী, আপনি আপনার অ্যাপকে সেই নির্দিষ্ট আইটেমটিতে লেখার অ্যাক্সেস দেওয়ার জন্য ব্যবহারকারীর কাছে অনুরোধ করতে পারেন:

কোটলিন

// Apply a grayscale filter to the image at the given content URI.
try {
    // "w" for write.
    contentResolver.openFileDescriptor(image-content-uri, "w")?.use {
        setGrayscaleFilter(it)
    }
} catch (securityException: SecurityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val recoverableSecurityException = securityException as?
            RecoverableSecurityException ?:
            throw RuntimeException(securityException.message, securityException)

        val intentSender =
            recoverableSecurityException.userAction.actionIntent.intentSender
        intentSender?.let {
            startIntentSenderForResult(intentSender, image-request-code,
                    null, 0, 0, 0, null)
        }
    } else {
        throw RuntimeException(securityException.message, securityException)
    }
}

জাভা

try {
    // "w" for write.
    ParcelFileDescriptor imageFd = getContentResolver()
            .openFileDescriptor(image-content-uri, "w");
    setGrayscaleFilter(imageFd);
} catch (SecurityException securityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        RecoverableSecurityException recoverableSecurityException;
        if (securityException instanceof RecoverableSecurityException) {
            recoverableSecurityException =
                    (RecoverableSecurityException)securityException;
        } else {
            throw new RuntimeException(
                    securityException.getMessage(), securityException);
        }
        IntentSender intentSender =recoverableSecurityException.getUserAction()
                .getActionIntent().getIntentSender();
        startIntentSenderForResult(intentSender, image-request-code,
                null, 0, 0, 0, null);
    } else {
        throw new RuntimeException(
                securityException.getMessage(), securityException);
    }
}

যখনই আপনার অ্যাপকে এমন কোনো মিডিয়া ফাইল পরিবর্তন করতে হবে যা সে নিজে তৈরি করেনি, তখন এই প্রক্রিয়াটি সম্পন্ন করুন।

বিকল্পভাবে, যদি আপনার অ্যাপটি অ্যান্ড্রয়েড ১১ বা তার উচ্চতর সংস্করণে চলে, তাহলে আপনি ব্যবহারকারীদেরকে একগুচ্ছ মিডিয়া ফাইলে আপনার অ্যাপকে লেখার অনুমতি (write access) দেওয়ার সুযোগ দিতে পারেন। মিডিয়া ফাইলের গ্রুপ কীভাবে পরিচালনা করতে হয় সেই অংশে বর্ণিত createWriteRequest() মেথডটি ব্যবহার করুন।

আপনার অ্যাপের যদি এমন কোনো ব্যবহারের ক্ষেত্র থাকে যা স্কোপড স্টোরেজের আওতাভুক্ত নয়, তাহলে একটি ফিচার রিকোয়েস্ট ফাইল করুন এবং সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট আউট করুন

একটি আইটেম সরান

মিডিয়া স্টোর থেকে আপনার অ্যাপের অপ্রয়োজনীয় কোনো আইটেম সরাতে, নিচের কোড স্নিপেটে দেখানো লজিকের অনুরূপ লজিক ব্যবহার করুন:

কোটলিন

// Remove a specific media item.
val resolver = applicationContext.contentResolver

// URI of the image to remove.
val imageUri = "..."

// WHERE clause.
val selection = "..."
val selectionArgs = "..."

// Perform the actual removal.
val numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs)

জাভা

// Remove a specific media item.
ContentResolver resolver = getApplicationContext()
        getContentResolver();

// URI of the image to remove.
Uri imageUri = "...";

// WHERE clause.
String selection = "...";
String[] selectionArgs = "...";

// Perform the actual removal.
int numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs);

যদি স্কোপড স্টোরেজ অনুপলব্ধ বা সক্রিয় না থাকে, তাহলে অন্য অ্যাপের মালিকানাধীন ফাইলগুলি সরাতে আপনি পূর্ববর্তী কোড স্নিপেটটি ব্যবহার করতে পারেন। তবে, যদি স্কোপড স্টোরেজ সক্রিয় থাকে, তাহলে মিডিয়া আইটেম আপডেট করার বিভাগে বর্ণিত পদ্ধতি অনুযায়ী, আপনার অ্যাপ যে প্রতিটি ফাইল সরাতে চায় তার জন্য একটি RecoverableSecurityException ক্যাচ করতে হবে।

আপনার অ্যাপটি যদি অ্যান্ড্রয়েড ১১ বা তার উচ্চতর সংস্করণে চলে, তাহলে আপনি ব্যবহারকারীদেরকে মুছে ফেলার জন্য একগুচ্ছ মিডিয়া ফাইল বেছে নেওয়ার সুযোগ দিতে পারেন। মিডিয়া ফাইলের গোষ্ঠী কীভাবে পরিচালনা করতে হয় সেই অংশে বর্ণিত পদ্ধতি অনুযায়ী, createTrashRequest() অথবা createDeleteRequest() মেথডটি ব্যবহার করুন।

আপনার অ্যাপের যদি এমন কোনো ব্যবহারের ক্ষেত্র থাকে যা স্কোপড স্টোরেজের আওতাভুক্ত নয়, তাহলে একটি ফিচার রিকোয়েস্ট ফাইল করুন এবং সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট আউট করুন

মিডিয়া ফাইলগুলির আপডেট সনাক্ত করুন

পূর্ববর্তী কোনো সময়ের তুলনায়, আপনার অ্যাপের এমন স্টোরেজ ভলিউমগুলো শনাক্ত করার প্রয়োজন হতে পারে যেগুলোতে অ্যাপ দ্বারা যোগ করা বা পরিবর্তন করা মিডিয়া ফাইল রয়েছে। এই পরিবর্তনগুলো সবচেয়ে নির্ভরযোগ্যভাবে শনাক্ত করতে, getGeneration() মেথডে কাঙ্ক্ষিত স্টোরেজ ভলিউমটি পাস করুন। যতক্ষণ মিডিয়া স্টোরের ভার্সন অপরিবর্তিত থাকে, ততক্ষণ এই মেথডের রিটার্ন ভ্যালু সময়ের সাথে সাথে ক্রমান্বয়ে বাড়তে থাকে।

বিশেষ করে, getGeneration() ফাংশনটি মিডিয়া কলামের তারিখ, যেমন DATE_ADDED এবং DATE_MODIFIED এর চেয়ে বেশি নির্ভরযোগ্য। এর কারণ হলো, যখন কোনো অ্যাপ setLastModified() কল করে অথবা যখন ব্যবহারকারী সিস্টেম ক্লক পরিবর্তন করে, তখন ওই মিডিয়া কলামের মানগুলো বদলে যেতে পারে।

মিডিয়া ফাইলের গ্রুপগুলি পরিচালনা করুন

অ্যান্ড্রয়েড ১১ এবং এর পরবর্তী সংস্করণগুলোতে, ব্যবহারকারীকে একগুচ্ছ মিডিয়া ফাইল নির্বাচন করতে বলে একবারে সেই ফাইলগুলো আপডেট করা যায়। এই পদ্ধতিগুলো বিভিন্ন ডিভাইসে আরও ভালো সামঞ্জস্য প্রদান করে এবং ব্যবহারকারীদের জন্য তাদের মিডিয়া সংগ্রহ পরিচালনা করা সহজ করে তোলে।

যে পদ্ধতিগুলো এই "ব্যাচ আপডেট" কার্যকারিতা প্রদান করে, সেগুলো হলো:

createWriteRequest()
ব্যবহারকারীকে অনুরোধ করুন যেন তিনি আপনার অ্যাপকে নির্দিষ্ট মিডিয়া ফাইল গ্রুপে লেখার অনুমতি দেন।
createFavoriteRequest()
ব্যবহারকারীকে অনুরোধ করা হচ্ছে যেন তিনি ডিভাইসে থাকা নির্দিষ্ট মিডিয়া ফাইলগুলোকে তাঁর 'প্রিয়' মিডিয়া হিসেবে চিহ্নিত করেন। যে কোনো অ্যাপ যার এই ফাইলটি পড়ার অ্যাক্সেস আছে, সে দেখতে পাবে যে ব্যবহারকারী ফাইলটিকে 'প্রিয়' হিসেবে চিহ্নিত করেছেন।
createTrashRequest()

ব্যবহারকারীকে নির্দিষ্ট মিডিয়া ফাইলগুলো ডিভাইসের ট্র্যাশে রাখার জন্য অনুরোধ করা হচ্ছে। সিস্টেম-নির্ধারিত সময় পর ট্র্যাশের আইটেমগুলো স্থায়ীভাবে মুছে ফেলা হয়।

createDeleteRequest()

ব্যবহারকারীকে অনুরোধ করা হচ্ছে যেন তিনি নির্দিষ্ট মিডিয়া ফাইলগুলো আগে থেকে ট্র্যাশে না রেখে অবিলম্বে স্থায়ীভাবে মুছে ফেলেন।

এই মেথডগুলোর যেকোনো একটি কল করার পর, সিস্টেম একটি PendingIntent অবজেক্ট তৈরি করে। আপনার অ্যাপ এই ইন্টেন্টটি ইনভোক করার পর, ব্যবহারকারীরা একটি ডায়ালগ দেখতে পান, যেখানে নির্দিষ্ট মিডিয়া ফাইলগুলো আপডেট বা ডিলিট করার জন্য তাদের অনুমতি চাওয়া হয়।

উদাহরণস্বরূপ, createWriteRequest() কলটি কীভাবে গঠন করতে হয় তা এখানে দেওয়া হলো:

কোটলিন

val urisToModify = /* A collection of content URIs to modify. */
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)

জাভা

List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
                  urisToModify);

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
    EDIT_REQUEST_CODE, null, 0, 0, 0);

ব্যবহারকারীর প্রতিক্রিয়া মূল্যায়ন করুন। যদি ব্যবহারকারী সম্মতি দিয়ে থাকেন, তবে মিডিয়া অপারেশনটি নিয়ে এগিয়ে যান। অন্যথায়, ব্যবহারকারীকে ব্যাখ্যা করুন কেন আপনার অ্যাপের এই অনুমতি প্রয়োজন:

কোটলিন

override fun onActivityResult(requestCode: Int, resultCode: Int,
                 data: Intent?) {
    ...
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}

জাভা

@Override
protected void onActivityResult(int requestCode, int resultCode,
                   @Nullable Intent data) {
    ...
    if (requestCode == EDIT_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            /* Edit request granted; proceed. */
        } else {
            /* Edit request not granted; explain to the user. */
        }
    }
}

আপনি এই একই সাধারণ প্যাটার্নটি createFavoriteRequest() , createTrashRequest() , এবং createDeleteRequest() এর ক্ষেত্রেও ব্যবহার করতে পারেন।

মিডিয়া ব্যবস্থাপনার অনুমতি

ব্যবহারকারীরা মিডিয়া ফাইল ঘন ঘন সম্পাদনা করার মতো মিডিয়া ব্যবস্থাপনার কাজ করার জন্য কোনো একটি নির্দিষ্ট অ্যাপের ওপর ভরসা করতে পারেন। যদি আপনার অ্যাপটি অ্যান্ড্রয়েড ১১ বা তার উচ্চতর সংস্করণকে লক্ষ্য করে তৈরি হয় এবং এটি ডিভাইসের ডিফল্ট গ্যালারি অ্যাপ না হয়, তবে প্রতিবার কোনো ফাইল পরিবর্তন বা মুছে ফেলার চেষ্টার সময় আপনাকে অবশ্যই ব্যবহারকারীকে একটি নিশ্চিতকরণ ডায়ালগ দেখাতে হবে।

আপনার অ্যাপটি যদি অ্যান্ড্রয়েড ১২ (এপিআই লেভেল ৩১) বা তার উচ্চতর সংস্করণকে টার্গেট করে, তাহলে আপনি ব্যবহারকারীদের কাছে আপনার অ্যাপের জন্য মিডিয়া ম্যানেজমেন্ট নামক বিশেষ পারমিশনটি মঞ্জুর করার অনুরোধ করতে পারেন। এই পারমিশনটি আপনার অ্যাপকে প্রতিটি ফাইল অপারেশনের জন্য ব্যবহারকারীকে জিজ্ঞাসা না করেই নিম্নলিখিত কাজগুলো করতে দেয়:

  • createWriteRequest() ব্যবহার করে ফাইলগুলো পরিবর্তন করুন।
  • createTrashRequest() ব্যবহার করে ট্র্যাশে ফাইল আনা-নেওয়া করুন।
  • createDeleteRequest() ব্যবহার করে ফাইলগুলো মুছে ফেলুন।

এটি করার জন্য, নিম্নলিখিত ধাপগুলো সম্পন্ন করুন:

  1. আপনার অ্যাপের ম্যানিফেস্ট ফাইলে MANAGE_MEDIA পারমিশন এবং READ_EXTERNAL_STORAGE পারমিশন ঘোষণা করুন।

    নিশ্চিতকরণ ডায়ালগ না দেখিয়ে createWriteRequest() কল করতে, ACCESS_MEDIA_LOCATION পারমিশনটিও ঘোষণা করুন।

  2. আপনার অ্যাপে ব্যবহারকারীকে একটি ইউজার ইন্টারফেস (UI) দেখান, যাতে ব্যাখ্যা করা যায় কেন তারা আপনার অ্যাপকে মিডিয়া ম্যানেজমেন্ট অ্যাক্সেস দিতে চাইতে পারেন।

  3. ACTION_REQUEST_MANAGE_MEDIA ইন্টেন্ট অ্যাকশনটি চালু করুন। এটি ব্যবহারকারীদের সিস্টেম সেটিংসের মিডিয়া ম্যানেজমেন্ট অ্যাপস স্ক্রিনে নিয়ে যাবে। এখান থেকে, ব্যবহারকারীরা বিশেষ অ্যাপটিকে অ্যাক্সেস দিতে পারবেন।

ব্যবহারের ক্ষেত্র যেখানে মিডিয়া স্টোরের বিকল্প প্রয়োজন

আপনার অ্যাপ যদি প্রধানত নিম্নলিখিত ভূমিকাগুলির মধ্যে একটি পালন করে, তাহলে MediaStore API-গুলির কোনো বিকল্প বিবেচনা করুন।

অন্যান্য ধরণের ফাইল নিয়ে কাজ করা

আপনার অ্যাপ যদি এমন সব ডকুমেন্ট ও ফাইল নিয়ে কাজ করে যেগুলিতে শুধুমাত্র মিডিয়া কন্টেন্ট থাকে না, যেমন EPUB বা PDF ফাইল এক্সটেনশন ব্যবহৃত হয়, তাহলে ডকুমেন্ট ও অন্যান্য ফাইল সংরক্ষণ এবং অ্যাক্সেস করার নির্দেশিকায় বর্ণিত ACTION_OPEN_DOCUMENT ইন্টেন্ট অ্যাকশনটি ব্যবহার করুন।

সহযোগী অ্যাপগুলিতে ফাইল শেয়ারিং

যেসব ক্ষেত্রে আপনি মেসেজিং অ্যাপ এবং প্রোফাইল অ্যাপের মতো একাধিক সহযোগী অ্যাপ সরবরাহ করেন, content:// URI ব্যবহার করে ফাইল শেয়ারিং সেট আপ করুন । আমরা এই কর্মপ্রক্রিয়াটিকে একটি সর্বোত্তম নিরাপত্তা অনুশীলন হিসেবেও সুপারিশ করি।

অতিরিক্ত সম্পদ

মিডিয়া কীভাবে সংরক্ষণ ও অ্যাক্সেস করতে হয় সে সম্পর্কে আরও তথ্যের জন্য নিম্নলিখিত রিসোর্সগুলো দেখুন।

নমুনা

ভিডিও