Skip to content

video_player aspect ratio and orientation messed up on iOS #17606

@rodydavis

Description

@rodydavis

Steps to Reproduce

  • Android Video Plays correctly in either portrait or landscape video modes
  • iOS Video is warped on portrait and landscape looks normal.
  • It is not accounting for orientation.
    when you record a video in landscape with the volume buttons facing up on iPhone and go to the video controller to play the video, it is upside down. If you take a video in landscape with the lock button facing up the video looks normal.

dependencies in pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  async: "^2.0.6"
  cupertino_icons: ^0.1.0
  url_launcher: "^3.0.0"
  intl: 0.15.4
  local_auth: "^0.2.0"
  shared_preferences: "^0.4.0"
  firebase_core: "^0.2.3"
  firebase_analytics: "^0.3.3"
  material_search: "^0.2.3"
  flutter_webview_plugin: "^0.1.5"
  device_info: "^0.2.0"
  path_provider: "^0.4.0"
  path: "^1.5.1"
  video_player: "0.5.2"
  dio: "^0.0.12"
  get_version:
    git:
      url: git://github.com/AppleEducate/get_version
  media_picker:
    git:
      url: git://github.com/AppleEducate/media_picker

Usage

import 'dart:async';
import 'package:async/async.dart';
import 'dart:io';
import 'package:path/path.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:media_picker/media_picker.dart';
import 'package:video_player/video_player.dart';
import 'package:Unify_Mobile/globals.dart' as globals;
import 'package:Unify_Mobile/menu/videodetails.dart';
import 'package:dio/dio.dart';

class CameraPage extends StatefulWidget {
  @override
  _CameraPageState createState() => new _CameraPageState();
}

// State for managing fetching name data over HTTP
class _CameraPageState extends State<CameraPage> {
  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  Future<File> _mediaFile;
  File _uploadFile;
  bool isVideo = false;
  VideoPlayerController _controller;
  VoidCallback listener;
  bool isPlaying = false;

  void _onImageButtonPressed(ImageSource source) {
    setState(() {
      if (_controller != null) {
        _controller.setVolume(0.0);
        _controller.removeListener(listener);
      }
      _mediaFile = MediaPicker.pickVideo(source: source).then((File _file) {
        _controller = VideoPlayerController.file(_file)
          ..addListener(listener)
          ..setVolume(1.0)
          ..initialize()
          ..setLooping(true)
          ..play();
        setState(() {
          isPlaying = true;
          _uploadFile = _file;
        });
      });
    });
  }

  @override
  void deactivate() {
    if (_controller != null) {
      _controller.setVolume(0.0);
      _controller.removeListener(listener);
    }
    super.deactivate();
  }

  @override
  void dispose() {
    if (_controller != null) {
      _controller.dispose();
    }
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    listener = () {
      setState(() {});
    };
  }

  Widget _previewVideo(VideoPlayerController controller) {
    if (controller == null) {
      isPlaying = false;
      return const Text('Please Select or Record a Video.');
    } else if (controller.value.initialized) {
      return InkWell(
          child: Padding(
            padding: const EdgeInsets.all(10.0),
            child: AspectRatio(
              aspectRatio:
                  controller.value.size.width / controller.value.size.height,
              child: VideoPlayer(
                controller,
              ),
            ),
          ),
          onTap: () {
            if (isPlaying) {
              _controller.pause();
              isPlaying = false;
            } else {
              _controller.play();
              isPlaying = true;
            }
          });
    } else {
      isPlaying = false;
      return const Text('Error Loading Video');
    }
  }

  Future uploadVideo(BuildContext context, File _uploadFile, String name,
      String description) async {
    final _name = name;
    final _description = description;
    if (_controller == null) {
      globals.Utility
          .showAlertPopup(context, "Info", "Video Required for Upload!");
    } else {
      final _ks = await globals.Utility.getKalturaSession();
      //  Add Video
      String _token = "";
      await http
          .get(
              'https://www.kaltura.com/api_v3/service/uploadtoken/action/add?ks=$_ks&format=1')
          .then((response) {
        Map<String, dynamic> _json = json.decode(response.body);
        _token = _json['id'].toString();
        print('"Token: $_token');
      });

      //  Upload Video
      String _url =
          "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?ks=$_ks&format=1&uploadTokenId=$_token&resume=false&finalChunk=true&resumeAt=-1";
      print("Path: " + _uploadFile.uri.toString());
      
      await uploadFile(_url, _uploadFile, 'fileData');

      //  Update Video Info (Name, Desctiption, Tags)
      var _formName = Uri.encodeQueryComponent(r'mediaEntry[name]');
      var _formDesription =
          Uri.encodeQueryComponent(r'mediaEntry[description]');
      var _formTags = Uri.encodeQueryComponent(r'mediaEntry[tags]');
      var _formCatagory = Uri.encodeQueryComponent(r'mediaEntry[categories]');
      var _formType = Uri.encodeQueryComponent(r'mediaEntry[mediaType]');
      String _request =
          'https://www.kaltura.com/api_v3/service/media/action/addFromUploadedFile?ks=$_ks&format=1&$_formTags=${globals.userCompanyID}&$_formName=$_name&$_formDesription=$_description&uploadTokenId=$_token&$_formCatagory=Unapproved&$_formType=1';
      print(_request);
      await http.get(_request).then((response) {
        print('Updated Info...\n' + response.body.toString());
        Map<String, dynamic> _json = json.decode(response.body);
        //  Verify Upload
         _scaffoldKey.currentState.hideCurrentSnackBar();
        globals.videoID = "" + _json["id"].toString();
        Navigator.pop(context);
      });
    }
  }

  Future uploadFile(String url, File file, String name) async {
      var stream =
          new http.ByteStream(DelegatingStream.typed(file.openRead()));
      var length = await file.length();
      var uri = Uri.parse(url);
      var request = new http.MultipartRequest("POST", uri);
      var multipartFile = new http.MultipartFile(name, stream, length,
          filename: basename(file.path));
      request.files.add(multipartFile);
      var response = await request.send();
      print(response.statusCode);
      response.stream.transform(utf8.decoder).listen((value) {
        print(value);
      });
  }

  void _uploadVideo(BuildContext context) async {
    final snackbar = new SnackBar(
      duration: new Duration(seconds: 20),
      content: new Row(
        children: <Widget>[
          new CircularProgressIndicator(),
          new Text("  Uploading...")
        ],
      ),
    );
    _scaffoldKey.currentState.showSnackBar(snackbar);
    await uploadVideo(context, _uploadFile, _nameController.text.toString(),
        _descController.text.toString());
    _scaffoldKey.currentState.hideCurrentSnackBar();
    //  Navigator.pop(context);
  }

  void handelTextChange(String value) {
    setState(() {});
  }

  final TextEditingController _nameController = new TextEditingController();
  final TextEditingController _descController = new TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: const Text('Video Upload'),
        backgroundColor: Colors.white,
        actions: <Widget>[
          new IconButton(
            icon: new Icon(
              Icons.help,
            ),
            onPressed: () {
              globals.Utility.showAlertPopup(context, 'Info',
                  "Please Record in Landscape Mode for Best Results.\n\nFor Higher Quality Videos and to Prevent Data Loss, Capture and Edit a Video in the Camera App then Select the Video Here.\n\nOnce the Video is Uploaded it will be Avaliable in the 'Videos' Section After it Completes Processing.");
            },
          ),
          new IconButton(
              icon: new Icon(Icons.file_upload),
              onPressed: () {
                if (_nameController.text.toString().isNotEmpty &&
                    _descController.text.toString().isNotEmpty) {
                  _uploadVideo(context);
                } else {
                  globals.Utility.showAlertPopup(
                      context, "Info", "Name and Description Required!");
                }
              })
        ],
      ),
      body: new SingleChildScrollView(
        child: new ListBody(
          children: <Widget>[
            new ListTile(
              title: new TextField(
                controller: _nameController,
                onChanged: handelTextChange,
                decoration: new InputDecoration(
                  hintText: "Name",
                ),
              ),
              trailing: _nameController.text.toString().isEmpty
                  ? new Icon(
                      Icons.error,
                      color: Colors.red,
                    )
                  : new Icon(
                      Icons.check_circle,
                      color: Colors.green,
                    ),
            ),
            new ListTile(
              title: new TextField(
                controller: _descController,
                onChanged: handelTextChange,
                decoration: new InputDecoration(
                  hintText: "Description",
                ),
              ),
              trailing: _descController.text.toString().isEmpty
                  ? new Icon(
                      Icons.error,
                      color: Colors.red,
                    )
                  : new Icon(
                      Icons.check_circle,
                      color: Colors.green,
                    ),
            ),
            new Container(
              height: 20.0,
            ),
            new Center(
              child: _previewVideo(_controller),
            )
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              backgroundColor: Colors.red,
              onPressed: () {
                _onImageButtonPressed(ImageSource.gallery);
              },
              heroTag: 'video0',
              tooltip: 'Pick Video from gallery',
              child: const Icon(Icons.video_library),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              backgroundColor: Colors.red,
              onPressed: () {
                _onImageButtonPressed(ImageSource.camera);
              },
              heroTag: 'video1',
              tooltip: 'Take a Video',
              child: const Icon(Icons.videocam),
            ),
          ),
        ],
      ),
    );
  }
}

Logs

Analyzing /Users/rodydavis/Documents/Github/MyUnifyVideo...
  hint • Unused import: 'package:Unify_Mobile/menu/videodetails.dart' at package:Unify_Mobile/camera.dart:15:8 • unused_import
  hint • Unused import: 'package:dio/dio.dart' at package:Unify_Mobile/camera.dart:16:8 • unused_import
  hint • The value of the field '_mediaFile' isn't used at package:Unify_Mobile/camera.dart:26:16 • unused_field
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:3:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:58:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:85:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:116:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:163:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:287:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom/custom_widgets.dart:462:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:3:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:58:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:85:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:116:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:163:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:237:7 • must_be_immutable
  hint • This class inherits from a class marked as @immutable, and therefore should be immutable (all instance fields must be final) at package:Unify_Mobile/custom_widgets.dart:412:7 • must_be_immutable
  hint • Duplicate import at package:Unify_Mobile/main.dart:5:8 • duplicate_import
  hint • Unused import: 'package:Unify_Mobile/menu/myaccount.dart' at package:Unify_Mobile/menu/contactsleads.dart:6:8 • unused_import
  hint • Unused import: 'package:material_search/material_search.dart' at package:Unify_Mobile/menu/contactsleads.dart:8:8 • unused_import
  hint • Unused import: 'package:flutter/scheduler.dart' at package:Unify_Mobile/menu/contactsleads.dart:9:8 • unused_import
  hint • Unused import: 'dart:convert' at package:Unify_Mobile/menu/help.dart:1:8 • unused_import
  hint • Unused import: 'package:flutter/rendering.dart' at package:Unify_Mobile/menu/help.dart:3:8 • unused_import
  hint • Unused import: 'dart:async' at package:Unify_Mobile/menu/help.dart:6:8 • unused_import
  hint • Unused import: 'dart:io' at package:Unify_Mobile/menu/help.dart:8:8 • unused_import
  hint • The method '_readAndroidBuildData' isn't used at package:Unify_Mobile/menu/help.dart:67:24 • unused_element
  hint • The method '_readIosDeviceInfo' isn't used at package:Unify_Mobile/menu/help.dart:97:24 • unused_element
  hint • Unused import: 'dart:convert' at package:Unify_Mobile/menu/settings.dart:1:8 • unused_import
  hint • Unused import: 'package:flutter/rendering.dart' at package:Unify_Mobile/menu/settings.dart:3:8 • unused_import
  hint • Unused import: 'package:Unify_Mobile/globals.dart' at package:Unify_Mobile/menu/settings.dart:4:8 • unused_import
  hint • Unused import: 'package:device_info/device_info.dart' at package:Unify_Mobile/menu/settings.dart:5:8 • unused_import
  hint • Unused import: 'dart:async' at package:Unify_Mobile/menu/settings.dart:6:8 • unused_import
  hint • Unused import: 'package:flutter/services.dart' at package:Unify_Mobile/menu/settings.dart:7:8 • unused_import
  hint • Unused import: 'package:Unify_Mobile/menu/myaccount.dart' at package:Unify_Mobile/menu/tabs.dart:6:8 • unused_import
  hint • Unused import: 'dart:io' at package:Unify_Mobile/menu/tabs.dart:11:8 • unused_import
  hint • The top level variable '_companyImageURL' isn't used at package:Unify_Mobile/menu/tabs.dart:31:14 • unused_element
  hint • The value of the field '_drawerContentsOpacity' isn't used at package:Unify_Mobile/menu/tabs.dart:38:21 • unused_field
  hint • The value of the field '_drawerDetailsPosition' isn't used at package:Unify_Mobile/menu/tabs.dart:40:21 • unused_field
  hint • The value of the field '_showDrawerContents' isn't used at package:Unify_Mobile/menu/tabs.dart:41:8 • unused_field
  hint • The method '_backIcon' isn't used at package:Unify_Mobile/menu/tabs.dart:104:12 • unused_element
  hint • The value of the field '_drawerContents' isn't used at package:Unify_Mobile/menu/tabs.dart:131:29 • unused_field
  hint • The value of the field '_drawerContentsIcons' isn't used at package:Unify_Mobile/menu/tabs.dart:136:29 • unused_field
  hint • The value of the local variable 'index' isn't used at package:Unify_Mobile/menu/tabs/archivedvideos.dart:99:9 • unused_local_variable
  hint • The value of the local variable 'index' isn't used at package:Unify_Mobile/menu/tabs/myvideos.dart:87:9 • unused_local_variable
  hint • The value of the local variable 'index' isn't used at package:Unify_Mobile/menu/tabs/sharedvideos.dart:99:9 • unused_local_variable
  hint • Unused import: 'package:flutter_webview_plugin/flutter_webview_plugin.dart' at package:Unify_Mobile/menu/videodetails.dart:7:8 • unused_import
  hint • The value of the local variable 'ccToggleBool' isn't used at package:Unify_Mobile/menu/videodetails.dart:443:12 • unused_local_variable
  hint • Unused import: 'package:device_info/device_info.dart' at package:Unify_Mobile/signin/forgot.dart:5:8 • unused_import
  hint • Unused import: 'package:flutter/services.dart' at package:Unify_Mobile/signin/forgot.dart:7:8 • unused_import
  hint • Unused import: 'package:get_version/get_version.dart' at package:Unify_Mobile/signin/forgot.dart:9:8 • unused_import
  hint • The value of the field '_controllerUsername' isn't used at package:Unify_Mobile/signin/forgot.dart:19:31 • unused_field
  hint • The value of the field '_controllerDomain' isn't used at package:Unify_Mobile/signin/forgot.dart:20:31 • unused_field
  hint • The value of the field '_scaffoldKey' isn't used at package:Unify_Mobile/signin/forgot.dart:22:9 • unused_field
  hint • Unused import: 'package:Unify_Mobile/menu/tabs.dart' at package:Unify_Mobile/signin/signin.dart:6:8 • unused_import
54 issues found.
(Ran in 7.4s)
[✓] Flutter (Channel beta, v0.3.2, on Mac OS X 10.13.4 17E199, locale en-US)
    • Flutter version 0.3.2 at /Users/rodydavis/Android/flutter/flutter
    • Framework revision 44b7e7d3f4 (4 weeks ago), 2018-04-20 01:02:44 -0700
    • Engine revision 09d05a3891
    • Dart version 2.0.0-dev.48.0.flutter-fe606f890b

[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    • Android SDK at /Users/rodydavis/Library/Android/sdk
    • Android NDK at /Users/rodydavis/Library/Android/sdk/ndk-bundle
    • Platform android-27, build-tools 27.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)
    • All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 9.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 9.3, Build version 9E145
    • ios-deploy 1.9.2
    • CocoaPods version 1.4.0

[✓] Android Studio (version 3.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 24.0.1
    • Dart plugin version 173.4700
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)

[✓] VS Code (version 1.23.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Dart Code extension version 2.12.1

[✓] Connected devices (3 available)
    • Rody’s 📱     • 68cab38f1e7de5b8aece2a1ebb7ca9a5d4761e19 • ios • iOS 11.3.1
    • Dev-iPhone-6s • a75015aff2fbecb29dcc8e459b14cba86c08f7fe • ios • iOS 11.3.1
    •               • 90288c0d9ae73afb8a84663c0475d208744fc569 • ios • iOS

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions