شارك المقالة

شرح مكتبة youtube_player_embed في Flutter لتشغيل فيديوهات YouTube داخل التطبيق

عند تطوير تطبيق Flutter قد تحتاج إلى عرض فيديوهات YouTube داخل التطبيق مباشرة، سواء كان التطبيق تعليميًا أو إخباريًا أو يحتوي على محتوى مرئي أو دورات تدريبية.

بدلًا من إرسال المستخدم إلى تطبيق YouTube أو فتح المتصفح الخارجي، يمكنك تضمين الفيديو داخل واجهة التطبيق والتحكم فيه من خلال Dart، مثل تشغيل الفيديو وإيقافه وكتم الصوت والانتقال إلى وقت معين ومعرفة حالة الفيديو أثناء التشغيل.

مكتبة youtube_player_embed توفر هذه الوظائف من خلال تضمين مشغل YouTube داخل InAppWebView مع مجموعة من الخيارات والأحداث التي تسمح لك بالتحكم في تجربة تشغيل الفيديو داخل تطبيق Flutter.

في هذا المقال سنتعرف على مكتبة youtube_player_embed بشكل كامل، وكيفية تثبيتها، وطريقة تشغيل فيديو YouTube، وكيفية تشغيل YouTube Shorts، وكيفية التحكم في الفيديو من خلال VideoController، وكيفية التعامل مع Events، بالإضافة إلى إعداد Android وiOS ومثال عملي كامل.

ما هي مكتبة youtube_player_embed؟

مكتبة youtube_player_embed هي Flutter Package مخصصة لتضمين فيديوهات YouTube وYouTube Shorts داخل تطبيقات Flutter باستخدام InAppWebView.

المكتبة توفر Widget باسم YoutubePlayerEmbed يمكن وضعه مباشرة داخل واجهة التطبيق، مع إمكانية تحديد Video ID، وتشغيل الفيديو تلقائيًا أو إيقاف التشغيل التلقائي، وكتم الصوت، وتحديد نسبة العرض إلى الارتفاع، وإخفاء بعض عناصر واجهة المشغل، والتعامل مع مجموعة من الأحداث.

وتوفر النسخة 1.6.4 أيضًا تحكمًا برمجيًا في تشغيل الفيديو من خلال VideoController، مثل Play وPause وSeek وMute وUnmute، بالإضافة إلى متابعة حالة الفيديو ووقت التشغيل.

مميزات مكتبة youtube_player_embed

  • تشغيل فيديوهات YouTube داخل تطبيق Flutter.
  • دعم YouTube Shorts.
  • تشغيل الفيديو تلقائيًا باستخدام Auto Play.
  • كتم الصوت أو إلغاء الكتم برمجيًا.
  • إيقاف وإعادة تشغيل الفيديو.
  • الانتقال إلى وقت معين داخل الفيديو باستخدام Seek.
  • التحكم بنسبة أبعاد الفيديو.
  • إمكانية إخفاء عناصر التحكم بالفيديو.
  • إمكانية التحكم في عرض صورة القناة.
  • إمكانية تفعيل أو تعطيل زر المشاركة.
  • إضافة عنوان مخصص للفيديو.
  • معرفة الوقت الحالي أثناء تشغيل الفيديو.
  • معرفة وقت تنفيذ Seek.
  • معرفة انتهاء الفيديو.
  • معرفة حالة الفيديو مثل Playing وPaused وMuted وFullscreen.
  • دعم Full Screen.

هذه الوظائف موثقة في API الخاصة بالحزمة وفي سجل التحديثات للإصدار 1.6.4.

تثبيت مكتبة youtube_player_embed

يمكنك إضافة المكتبة إلى مشروع Flutter من خلال الأمر التالي:

flutter pub add youtube_player_embed

أو إضافتها يدويًا إلى ملف pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  youtube_player_embed: ^1.6.4

ثم قم بتشغيل:

flutter pub get

الإصدار المستقر الظاهر على pub.dev هو 1.6.4، وتعتمد المكتبة على flutter_inappwebview.

استيراد المكتبة

بعد تثبيت المكتبة يمكنك استيرادها داخل ملف Dart:

import 'package:youtube_player_embed/youtube_player_embed.dart';

وعند الحاجة إلى التحكم بالفيديو يمكنك أيضًا استيراد:

import 'package:youtube_player_embed/controller/video_controller.dart';

import 'package:youtube_player_embed/enum/video_state.dart';

ما هو YouTube Video ID؟

المكتبة تحتاج إلى videoId بدلًا من إرسال رابط YouTube كامل إلى Widget.

على سبيل المثال إذا كان رابط الفيديو:

https://www.youtube.com/watch?v=dQw4w9WgXcQ

فإن Video ID هو الجزء:

dQw4w9WgXcQ

ويتم تمريره إلى:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
)

والـ API الخاصة بالمكتبة تحدد videoId كقيمة مطلوبة.

تشغيل أول فيديو YouTube في Flutter

أبسط طريقة لعرض فيديو هي:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
)

ويمكن وضع الـ Widget داخل:

Column(
  children: [
    YoutubePlayerEmbed(
      videoId: 'dQw4w9WgXcQ',
    ),
  ],
)

بهذه الطريقة يمكن للمستخدم مشاهدة الفيديو داخل واجهة التطبيق بدل الخروج إلى تطبيق YouTube أو المتصفح.

إيقاف Auto Play

الخاصية autoPlay تكون مفعلة افتراضيًا في الـ Widget وفق API الحالية، ويمكن تعطيلها:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  autoPlay: false,
)

وهذا مفيد إذا كنت تريد أن يبدأ الفيديو فقط عندما يضغط المستخدم على زر التشغيل.

تشغيل الفيديو تلقائيًا

يمكن كذلك تفعيل التشغيل التلقائي بشكل صريح:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  autoPlay: true,
)

ومع ذلك، سلوك التشغيل التلقائي قد يتأثر بسياسات WebView والمنصة نفسها، لذلك يمكن أن يختلف السلوك العملي حسب الجهاز وإعدادات النظام.

كتم صوت الفيديو

توفر المكتبة الخاصية mute:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  mute: true,
)

ويمكن استخدامها عندما تريد تشغيل الفيديو بشكل صامت عند فتح الشاشة.

تحديد Aspect Ratio للفيديو

من أهم الخيارات عند تضمين فيديوهات YouTube هو تحديد أبعاد الفيديو.

فيديوهات YouTube التقليدية تستخدم غالبًا نسبة:

16 / 9

ويمكن كتابة:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  aspectRatio: 16 / 9,
)

أما المحتوى العمودي مثل Shorts فيمكن استخدام:

YoutubePlayerEmbed(
  videoId: 'VIDEO_ID',
  aspectRatio: 9 / 16,
)

توثيق المكتبة يذكر دعم النسب الأفقية والعمودية، بما في ذلك استخدام المكتبة مع YouTube Shorts.

تشغيل YouTube Shorts

من الوظائف التي تدعمها المكتبة تضمين YouTube Shorts باستخدام Video ID الخاص بالفيديو.

على سبيل المثال يمكن استخدام:

YoutubePlayerEmbed(
  videoId: 'SHORT_VIDEO_ID',
  aspectRatio: 9 / 16,
)

وهذا يجعل المكتبة مناسبة للتطبيقات التي تعرض محتوى YouTube التقليدي إلى جانب الفيديوهات العمودية.

إخفاء عناصر التحكم في الفيديو

إذا كنت تريد إخفاء عناصر التحكم الموجودة داخل مشغل الفيديو، يمكنك استخدام:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  hidenVideoControls: true,
)

والخاصية الموجودة في API تحمل اسم hidenVideoControls بهذه الكتابة تحديدًا.

إخفاء صورة القناة

يمكن التحكم في ظهور صورة القناة من خلال:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  hidenChannelImage: true,
)

وتوضح API أن القيمة الافتراضية الحالية لهذه الخاصية هي true.

إظهار أو إخفاء زر المشاركة

إذا أردت تمكين زر المشاركة:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  enabledShareButton: true,
)

أما بشكل افتراضي فالقيمة هي false.

إضافة عنوان مخصص للفيديو

يمكنك تحديد عنوان مخصص أعلى الفيديو باستخدام:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  customVideoTitle: 'شرح Flutter للمبتدئين',
)

وتوفر النسخة 1.6.4 دعمًا للعناوين المخصصة.

الحصول على VideoController

من الوظائف المهمة في المكتبة إمكانية الحصول على VideoController من خلال Callback.

VideoController? videoController;

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  callBackVideoController: (controller) {
    videoController = controller;
  },
)

بعد الحصول على Controller يمكنك استخدامه للتحكم بالفيديو من خارج الـ Widget. وهذا الأسلوب مستخدم أيضًا في المثال الرسمي للحزمة.

تشغيل الفيديو باستخدام VideoController

بعد الحصول على الـ Controller يمكنك تشغيل الفيديو برمجيًا:

await videoController?.playVideo();

ويمكن وضعه داخل زر:

ElevatedButton(
  onPressed: () async {
    await videoController?.playVideo();
  },
  child: const Text('Play'),
)

الدالة playVideo() موضحة ضمن مثال الاستخدام الرسمي.

إيقاف الفيديو

يمكن إيقاف الفيديو باستخدام:

await videoController?.pauseVideo();

مثال:

ElevatedButton(
  onPressed: () async {
    await videoController?.pauseVideo();
  },
  child: const Text('Pause'),
)

وتوفر المكتبة هذا التحكم كجزء من VideoController.

كتم وإلغاء كتم الصوت

يمكن استخدام:

await videoController?.muteOrUnmuteVideo();

مثل:

ElevatedButton(
  onPressed: () async {
    await videoController?.muteOrUnmuteVideo();
  },
  child: const Text('Mute / Unmute'),
)

وهو أيضًا جزء من مثال الحزمة الرسمي.

الانتقال إلى وقت معين داخل الفيديو

إذا أردت الانتقال إلى الثانية الرابعة مثلًا:

await videoController?.seekTo(
  time: 4,
);

ويمكن تعديل القيمة بحسب الوقت المطلوب:

await videoController?.seekTo(
  time: 120,
);

وهنا سينتقل الفيديو إلى الثانية 120. المثال الرسمي يستخدم نفس API مع seekTo.

معرفة انتهاء الفيديو

يمكن الاستماع إلى حدث انتهاء الفيديو من خلال:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  onVideoEnd: () {
    debugPrint('Video ended');
  },
)

ويمكن استخدام هذا الحدث للانتقال تلقائيًا إلى الفيديو التالي أو تحديث واجهة التطبيق أو تسجيل مشاهدة الفيديو.

معرفة وقت الفيديو الحالي

توفر المكتبة onVideoTimeUpdate:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  onVideoTimeUpdate: (currentTime) {
    debugPrint(
      'Current time: $currentTime',
    );
  },
)

وهذا يسمح لك بمعرفة موقع المستخدم الحالي داخل الفيديو أثناء التشغيل.

معرفة وقت Seek

يمكنك معرفة متى قام المستخدم بالانتقال إلى وقت جديد:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  onVideoSeek: (currentTime) {
    debugPrint(
      'Seeked to: $currentTime seconds',
    );
  },
)

وهذا مفيد في تطبيقات التعليم والدورات التدريبية عندما تحتاج إلى مراقبة تقدم المستخدم داخل الفيديو.

معرفة حالة الفيديو

يمكن استخدام:

onVideoStateChange

لمراقبة تغير الحالة:

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  onVideoStateChange: (state) {
    debugPrint(
      'Video state: $state',
    );
  },
)

وتوفر المكتبة حالات مثل:

  • VideoState.playing
  • VideoState.paused
  • VideoState.muted
  • VideoState.unmuted
  • VideoState.fullscreen
  • VideoState.normalView

هذه الحالات ظاهرة ضمن المثال الرسمي للحزمة.

مثال على التعامل مع VideoState

onVideoStateChange: (state) {
  switch (state) {
    case VideoState.playing:
      debugPrint('Video is playing');
      break;

    case VideoState.paused:
      debugPrint('Video is paused');
      break;

    case VideoState.muted:
      debugPrint('Video is muted');
      break;

    case VideoState.unmuted:
      debugPrint('Video is unmuted');
      break;

    case VideoState.fullscreen:
      debugPrint('Video is fullscreen');
      break;

    case VideoState.normalView:
      debugPrint('Video is in normal view');
      break;
  }
}

دعم Full Screen

تدعم المكتبة الانتقال بين العرض الطبيعي وFull Screen، ويمكنك مراقبة ذلك من خلال:

VideoState.fullscreen

VideoState.normalView

ويمكنك الاستفادة من هذه الأحداث لتغيير تصميم الشاشة أو إخفاء بعض عناصر الواجهة عند تشغيل الفيديو في وضع ملء الشاشة.

تشغيل قائمة من فيديوهات YouTube

يمكن استخدام المكتبة أيضًا عند وجود مجموعة من الفيديوهات داخل التطبيق.

مثل:

final List<String> videos = [
  'pUb9EW770d0',
  '3vPbzEhF63s',
];

وهو نفس المفهوم المستخدم في المثال الرسمي للحزمة عند التعامل مع أكثر من فيديو.

وعند تغيير الفيديو يفضل إعطاء Widget مفتاحًا مختلفًا، مثل:

YoutubePlayerEmbed(
  key: ValueKey(currentVideo),
  videoId: currentVideo,
)

وهذا يساعد Flutter على التعامل مع الفيديو الجديد كعنصر مختلف، وهو أسلوب مستخدم في مثال الحزمة الرسمي.

استخراج Video ID من رابط YouTube

المكتبة تحتاج إلى Video ID، لذلك إذا كانت البيانات القادمة من API عبارة عن روابط YouTube كاملة، يمكنك استخراج الـ ID قبل تمريره إلى Widget.

مثل الرابط:

https://www.youtube.com/watch?v=dQw4w9WgXcQ

يمكن استخدام:

String? extractYoutubeVideoId(
  String url,
) {
  final uri = Uri.tryParse(url);

  if (uri == null) {
    return null;
  }

  if (uri.host.contains('youtube.com')) {
    return uri.queryParameters['v'];
  }

  if (uri.host == 'youtu.be') {
    if (uri.pathSegments.isNotEmpty) {
      return uri.pathSegments.first;
    }
  }

  return null;
}

ثم:

final videoId = extractYoutubeVideoId(
  'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
);

if (videoId != null) {
  debugPrint(videoId);
}

التعامل مع YouTube Shorts URL

يمكن أن يكون الرابط:

https://www.youtube.com/shorts/VIDEO_ID

ويمكن توسيع الدالة السابقة:

String? extractYoutubeVideoId(
  String url,
) {
  final uri = Uri.tryParse(url);

  if (uri == null) {
    return null;
  }

  if (uri.host.contains('youtube.com')) {
    if (uri.pathSegments.isNotEmpty &&
        uri.pathSegments.first == 'shorts' &&
        uri.pathSegments.length > 1) {
      return uri.pathSegments[1];
    }

    return uri.queryParameters['v'];
  }

  if (uri.host == 'youtu.be') {
    if (uri.pathSegments.isNotEmpty) {
      return uri.pathSegments.first;
    }
  }

  return null;
}

بعد ذلك يمكنك تمرير Video ID إلى YoutubePlayerEmbed مع aspectRatio: 9 / 16 للمحتوى العمودي. دعم Shorts والنسب العمودية مذكور ضمن توثيق الحزمة.

استخدام المكتبة في تطبيقات التعليم

تعتبر المكتبة مناسبة للتطبيقات التي تحتوي على دورات أو دروس فيديو.

يمكنك مثلًا تنفيذ:

  • تشغيل الفيديو داخل صفحة الدرس.
  • معرفة وقت المستخدم الحالي.
  • معرفة وقت انتهاء الفيديو.
  • عرض الدرس التالي بعد انتهاء الفيديو.
  • حفظ تقدم المشاهدة.
  • إضافة أزرار تحكم خارج مشغل الفيديو.

ويمكن ربط onVideoTimeUpdate أو onVideoEnd مع API لحفظ حالة المستخدم.

مثال على حفظ تقدم المشاهدة

YoutubePlayerEmbed(
  videoId: 'dQw4w9WgXcQ',
  onVideoTimeUpdate: (currentTime) {
    debugPrint(
      'Watch time: $currentTime',
    );

    // أرسل الوقت إلى API عند الحاجة
  },
)

التعامل مع الفيديو التالي

إذا كان لديك مجموعة من الدروس، يمكن الانتقال إلى الفيديو التالي عند انتهاء الفيديو الحالي:

onVideoEnd: () {
  if (currentIndex < videos.length - 1) {
    setState(() {
      currentIndex++;
    });
  }
}

وبما أن videoId سيتغير، يمكنك استخدام ValueKey المرتبط بالفيديو الحالي:

YoutubePlayerEmbed(
  key: ValueKey(videos[currentIndex]),
  videoId: videos[currentIndex],
  autoPlay: true,
)

إعداد Android

وفق توثيق المكتبة، تحتاج Android إلى صلاحيات الشبكة التالية داخل:

android/app/src/main/AndroidManifest.xml
<uses-permission
    android:name="android.permission.INTERNET" />

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

هذه الصلاحيات مذكورة ضمن قسم إعداد Android في توثيق الحزمة.

إعداد iOS

تستخدم المكتبة flutter_inappwebview، ولهذا قد تحتاج إلى بعض إعدادات iOS الإضافية بحسب إصدار المشروع والبيئة.

توثيق الحزمة يذكر إضافة:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

<key>io.flutter.embedded_views_preview</key>
<true/>

إلى:

ios/Runner/Info.plist

كما يذكر توثيق المكتبة أنه إذا ظهرت مشاكل مرتبطة بـ flutter_inappwebview فقد تحتاج إلى تعديل Podfile واستخدام use_frameworks! وبعض إعدادات Swift وBitcode.

تعديل Podfile في iOS عند الحاجة

target 'Runner' do
  use_frameworks!

  # باقي إعدادات المشروع
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '5.0'
      config.build_settings['ENABLE_BITCODE'] = 'NO'
    end
  end
end

هذا الإعداد مذكور في توثيق الحزمة كحل عند ظهور بعض مشكلات iOS المرتبطة بـ flutter_inappwebview.

استخدام VideoController مع أزرار خارجية

من المميزات المفيدة في المكتبة أنك لا تحتاج إلى الاعتماد فقط على أزرار YouTube الداخلية، وإنما يمكنك بناء واجهتك الخاصة.

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    IconButton(
      onPressed: () async {
        await videoController?.playVideo();
      },
      icon: const Icon(Icons.play_arrow),
    ),

    IconButton(
      onPressed: () async {
        await videoController?.pauseVideo();
      },
      icon: const Icon(Icons.pause),
    ),

    IconButton(
      onPressed: () async {
        await videoController?.muteOrUnmuteVideo();
      },
      icon: const Icon(Icons.volume_up),
    ),
  ],
)

وبهذه الطريقة تستطيع تصميم Player UI متوافقًا مع تصميم تطبيقك.

استخدام المكتبة مع API

يمكن أن تكون بيانات الفيديو قادمة من Backend مثل:

{
  "id": 15,
  "title": "شرح Flutter",
  "youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}

ثم استخراج Video ID:

final videoId = extractYoutubeVideoId(
  video.youtubeUrl,
);

ثم:

YoutubePlayerEmbed(
  videoId: videoId!,
  customVideoTitle: video.title,
  aspectRatio: 16 / 9,
)

بهذا الأسلوب يصبح محتوى الفيديو ديناميكيًا بدل كتابة Video ID داخل الكود.

التعامل مع الفيديو غير الصحيح

أضاف الإصدار 1.6.4 تحسينات في التعامل مع Video IDs غير الصحيحة وحالات الخطأ المرتبطة بالفيديو.

ومع ذلك من الأفضل قبل عرض الفيديو التأكد من أن قيمة videoId ليست فارغة وأن البيانات القادمة من API صحيحة.

if (videoId == null || videoId.isEmpty) {
  return const Center(
    child: Text(
      'Invalid YouTube video',
    ),
  );
}

استخدام المكتبة في تطبيقات Flutter

يمكن استخدام youtube_player_embed في أنواع مختلفة من التطبيقات، مثل:

  • تطبيقات الدورات التعليمية.
  • تطبيقات الأخبار والمحتوى المرئي.
  • تطبيقات القرآن والمحتوى الديني.
  • تطبيقات الطبخ والوصفات.
  • تطبيقات التدريب واللياقة.
  • تطبيقات المحتوى التعليمي.
  • تطبيقات عرض YouTube Shorts.
  • صفحات تفاصيل المنتجات التي تحتوي على فيديو.
  • التطبيقات التي تعرض فيديوهات من Backend.

الفرق بين youtube_player_embed وفتح YouTube خارجيًا

عند فتح الفيديو خارجيًا، ينتقل المستخدم إلى تطبيق YouTube أو المتصفح ويغادر تجربة التطبيق.

أما باستخدام youtube_player_embed فيمكن وضع الفيديو داخل نفس الصفحة والتحكم في تشغيله من Flutter، بالإضافة إلى متابعة حالة الفيديو والأحداث المرتبطة به.

وهذا يكون مفيدًا عندما يكون الفيديو جزءًا من تجربة المستخدم الرئيسية وليس مجرد رابط خارجي.

مثال كامل لشاشة YouTube Player

import 'package:flutter/material.dart';

import 'package:youtube_player_embed/controller/video_controller.dart';
import 'package:youtube_player_embed/enum/video_state.dart';
import 'package:youtube_player_embed/youtube_player_embed.dart';

class YoutubeVideoPage extends StatefulWidget {
  const YoutubeVideoPage({
    super.key,
  });

  @override
  State<YoutubeVideoPage> createState() =>
      _YoutubeVideoPageState();
}

class _YoutubeVideoPageState
    extends State<YoutubeVideoPage> {
  VideoController? videoController;

  String currentState = 'normal';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'YouTube Player',
        ),
      ),
      body: Column(
        children: [
          YoutubePlayerEmbed(
            key: const ValueKey(
              'dQw4w9WgXcQ',
            ),
            videoId: 'dQw4w9WgXcQ',

            callBackVideoController:
                (controller) {
              videoController = controller;
            },

            customVideoTitle:
                'Flutter YouTube Player',

            autoPlay: false,

            mute: false,

            hidenVideoControls: false,

            hidenChannelImage: true,

            enabledShareButton: false,

            aspectRatio: 16 / 9,

            onVideoEnd: () {
              debugPrint(
                'Video ended',
              );
            },

            onVideoSeek: (currentTime) {
              debugPrint(
                'Seeked to $currentTime',
              );
            },

            onVideoTimeUpdate:
                (currentTime) {
              debugPrint(
                'Current time: $currentTime',
              );
            },

            onVideoStateChange: (state) {
              setState(() {
                currentState =
                    state.toString();
              });

              switch (state) {
                case VideoState.playing:
                  debugPrint(
                    'Playing',
                  );
                  break;

                case VideoState.paused:
                  debugPrint(
                    'Paused',
                  );
                  break;

                case VideoState.muted:
                  debugPrint(
                    'Muted',
                  );
                  break;

                case VideoState.unmuted:
                  debugPrint(
                    'Unmuted',
                  );
                  break;

                case VideoState.fullscreen:
                  debugPrint(
                    'Fullscreen',
                  );
                  break;

                case VideoState.normalView:
                  debugPrint(
                    'Normal View',
                  );
                  break;
              }
            },
          ),

          const SizedBox(
            height: 30,
          ),

          Text(
            'State: $currentState',
          ),

          const SizedBox(
            height: 20,
          ),

          Wrap(
            spacing: 10,
            runSpacing: 10,
            alignment:
                WrapAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () async {
                  await videoController
                      ?.playVideo();
                },
                child: const Text(
                  'Play',
                ),
              ),

              ElevatedButton(
                onPressed: () async {
                  await videoController
                      ?.pauseVideo();
                },
                child: const Text(
                  'Pause',
                ),
              ),

              ElevatedButton(
                onPressed: () async {
                  await videoController
                      ?.muteOrUnmuteVideo();
                },
                child: const Text(
                  'Mute / Unmute',
                ),
              ),

              ElevatedButton(
                onPressed: () async {
                  await videoController
                      ?.seekTo(
                    time: 30,
                  );
                },
                child: const Text(
                  'Seek 30s',
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

مثال لتشغيل YouTube Shorts

YoutubePlayerEmbed(
  key: const ValueKey(
    'SHORT_VIDEO_ID',
  ),
  videoId: 'SHORT_VIDEO_ID',
  autoPlay: false,
  aspectRatio: 9 / 16,
  mute: false,
  hidenVideoControls: false,
  enabledShareButton: false,
  customVideoTitle: 'YouTube Short',
)

مثال لإنشاء قائمة فيديوهات

class VideosPage extends StatefulWidget {
  const VideosPage({
    super.key,
  });

  @override
  State<VideosPage> createState() =>
      _VideosPageState();
}

class _VideosPageState
    extends State<VideosPage> {
  final List<String> videos = [
    'pUb9EW770d0',
    '3vPbzEhF63s',
  ];

  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    final currentVideo =
        videos[currentIndex];

    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Videos',
        ),
      ),
      body: Column(
        children: [
          YoutubePlayerEmbed(
            key: ValueKey(
              currentVideo,
            ),
            videoId: currentVideo,
            autoPlay: false,
            aspectRatio: 16 / 9,

            onVideoEnd: () {
              if (currentIndex <
                  videos.length - 1) {
                setState(() {
                  currentIndex++;
                });
              }
            },
          ),

          const SizedBox(
            height: 20,
          ),

          Text(
            'Video ${currentIndex + 1}',
          ),
        ],
      ),
    );
  }
}

الخلاصة

مكتبة youtube_player_embed توفر طريقة مباشرة لتضمين فيديوهات YouTube داخل تطبيقات Flutter من خلال Widget باسم YoutubePlayerEmbed، مع إمكانية تخصيص طريقة عرض الفيديو والتحكم فيه برمجيًا.

وتتميز المكتبة بأنها لا تكتفي بعرض الفيديو، بل توفر VideoController للتحكم في التشغيل والإيقاف والكتم والانتقال إلى وقت معين، بالإضافة إلى Events تسمح بمعرفة حالة الفيديو ووقت التشغيل والانتهاء وعمليات Seek.

كما تدعم المكتبة YouTube Shorts من خلال استخدام Video ID الخاص بالفيديو مع إمكانية ضبط aspectRatio إلى 9 / 16 للمحتوى العمودي.

إذا كان مشروع Flutter الخاص بك يعتمد على الفيديوهات التعليمية أو المحتوى المرئي أو تريد دمج YouTube داخل تجربة التطبيق بدل فتحه خارجيًا، فإن youtube_player_embed توفر مجموعة من الأدوات الأساسية التي تساعدك على بناء هذه التجربة.



مكتبة youtube_player_embed على pub.dev

توثيق youtube_player_embed

المزيد من مقالات Flutter على Geecoders

الكود كامل

import 'package:flutter/material.dart';

import 'package:youtube_player_embed/controller/video_controller.dart';
import 'package:youtube_player_embed/enum/video_state.dart';
import 'package:youtube_player_embed/youtube_player_embed.dart';

void main() {
  runApp(
    const MyApp(),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'YouTube Player Embed',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.red,
        ),
        useMaterial3: true,
      ),
      home: const YoutubeVideoPage(),
    );
  }
}

class YoutubeVideoPage
    extends StatefulWidget {
  const YoutubeVideoPage({
    super.key,
  });

  @override
  State<YoutubeVideoPage> createState() =>
      _YoutubeVideoPageState();
}

class _YoutubeVideoPageState
    extends State<YoutubeVideoPage> {
  VideoController? videoController;

  VideoState? currentState;

  String? currentTime;

  String? seekTime;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'YouTube Player Embed',
        ),
      ),
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.all(
            16,
          ),
          children: [
            YoutubePlayerEmbed(
              key: const ValueKey(
                'dQw4w9WgXcQ',
              ),
              videoId:
                  'dQw4w9WgXcQ',

              callBackVideoController:
                  (controller) {
                videoController =
                    controller;
              },

              customVideoTitle:
                  'Flutter YouTube Video',

              autoPlay: false,

              mute: false,

              hidenVideoControls: false,

              hidenChannelImage: true,

              enabledShareButton: false,

              aspectRatio: 16 / 9,

              onVideoEnd: () {
                debugPrint(
                  'Video ended',
                );
              },

              onVideoSeek: (time) {
                setState(() {
                  seekTime =
                      time.toString();
                });
              },

              onVideoTimeUpdate: (time) {
                setState(() {
                  currentTime =
                      time.toString();
                });
              },

              onVideoStateChange: (state) {
                setState(() {
                  currentState =
                      state;
                });

                switch (state) {
                  case VideoState.playing:
                    debugPrint(
                      'Video is playing',
                    );
                    break;

                  case VideoState.paused:
                    debugPrint(
                      'Video is paused',
                    );
                    break;

                  case VideoState.muted:
                    debugPrint(
                      'Video is muted',
                    );
                    break;

                  case VideoState.unmuted:
                    debugPrint(
                      'Video is unmuted',
                    );
                    break;

                  case VideoState.fullscreen:
                    debugPrint(
                      'Video is fullscreen',
                    );
                    break;

                  case VideoState.normalView:
                    debugPrint(
                      'Video is in normal view',
                    );
                    break;
                }
              },
            ),

            const SizedBox(
              height: 24,
            ),

            Card(
              child: Padding(
                padding:
                    const EdgeInsets.all(16),
                child: Column(
                  children: [
                    Text(
                      'State: ${currentState ?? '-'}',
                    ),

                    const SizedBox(
                      height: 8,
                    ),

                    Text(
                      'Current Time: '
                      '${currentTime ?? '-'}',
                    ),

                    const SizedBox(
                      height: 8,
                    ),

                    Text(
                      'Last Seek: '
                      '${seekTime ?? '-'}',
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(
              height: 24,
            ),

            Wrap(
              spacing: 10,
              runSpacing: 10,
              alignment:
                  WrapAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: () async {
                    await videoController
                        ?.playVideo();
                  },
                  icon: const Icon(
                    Icons.play_arrow,
                  ),
                  label: const Text(
                    'Play',
                  ),
                ),

                ElevatedButton.icon(
                  onPressed: () async {
                    await videoController
                        ?.pauseVideo();
                  },
                  icon: const Icon(
                    Icons.pause,
                  ),
                  label: const Text(
                    'Pause',
                  ),
                ),

                ElevatedButton.icon(
                  onPressed: () async {
                    await videoController
                        ?.muteOrUnmuteVideo();
                  },
                  icon: const Icon(
                    Icons.volume_up,
                  ),
                  label: const Text(
                    'Mute / Unmute',
                  ),
                ),

                ElevatedButton.icon(
                  onPressed: () async {
                    await videoController
                        ?.seekTo(
                      time: 30,
                    );
                  },
                  icon: const Icon(
                    Icons.forward_30,
                  ),
                  label: const Text(
                    'Seek 30s',
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  youtube_player_embed: ^1.6.4

AndroidManifest.xml

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android">

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

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

    <application
        android:label="youtube_player_demo"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop">

            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />

                <category
                    android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>

    </application>

</manifest>

Info.plist

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

<key>io.flutter.embedded_views_preview</key>
<true/>

مزيد من المقالات : كيفية ربط Hala POS في مشروع أندرويد خطوة بخطوة
شاهد أيضًا
مقالات ذات صلة

🚫 مانع الإعلانات مفعل

يجب إيقاف مانع الإعلانات لاستكمال تصفح الموقع