Thursday, 31 January 2013

Are Hangouts Any Good For Presentations?


In recent posts I've described how we've been using Google Hangouts to stream Archaeology seminars. These have been simple point-the-laptop-at-the-speaker-and-press-go affairs. Because we had a real live and remote audience at the the last seminar I've been wondering if we could do a better job of displaying the presenter's slides rather than just the webcam video of the speaker.

At first glance it would seem that there are two ways of displaying the speakers slides. The first would be using the Google Drive tool. But when you select that tool, you are sharing the "editing" view of your slides. Not what you want at all. If you choose "Present" the app goes full screen and you lose the Hangout you're in.

The other option might be to use the Screenshare tool, but the same thing applies. You can see my slides below are being shown in a shared window, but it is with the addition of the slide thumbnails, the speaker notes and even the browser  tabs and window bar. Again, when you click present, it takes over your entire screen(s), adds a lovely brushed metal effect and blots out your Hangout. 

This way of working would be OK if you were working on the slides together, but it seems hopeless for a presenter presenting.  I had thought this might be a good way for long listed job candidates, many who might be overseas, to present for a pre-interview perhaps.




Is it me?


Is this just a Mac thing? Has anyone else satisfactorily presented slides using a Hangout? Am I missing a setting or doing something completely wrong?

For me, the way it should/could work would be for a presentation to simply fill the white area of the screen with the tools down the side remaining ( and the screen controller at bottom ). If they were being clever, I'd maybe be able to doodle on the slides. Ideally the slides would stretch to fit.. or fit the widest edge. Maybe the background colour would be detected ( to make the slides look nice ) etc.

At the moment there is so much interface clutter that the slides themselves are like a postcard propped on a mantel piece at the end of a corridor filled with filling cabinets and signage and chairs, maybe a bin or two, with lever arch box files stacked randomly here and there. The experience is like a bureaucratic pop video, obsessing on the tools and missing the action.

Problems adding events in Google Calendar

Anyone else get this sort of thing when adding Google Calendar events? ....  which often results in an event just disappearing.







Hang on, I've also been getting this...






Wednesday, 30 January 2013

Using Google Hangouts (On Air) To Stream Archaeology Seminars


+Sara Perry wanted to use Google Hangouts to quickly and easily record seminars for students that can't make it in person and to help with promoting the work of the Archaeology dept. 

We decided on using minimal tech intervention. No requirement to use a certain presentation tool. No microphones etc.

Many presenters are easily spooked by extra technology, especially when speaking,  but we also want this to be an easy enough process that Sara will be able to make sure it happens without needing any preparation.  

This cheap and cheerful approach also guides the aesthetics of the video, we aren't planning to add titles, idents, logos etc. We tested a MacBook Pro that was close to the presenters pedestal and found the internal mic in that was "good enough" for a small presentation room. 

We did buy a cheap webcam and a long USB cable so that we didn't have to use the camera on her laptop. This means the video capture can "step back" a little and take in more of the room.


Google Hangouts vs Hangouts "On Air"

Google Hangouts are simply video conferences. You can create a Hangout in Google+ or from within a Calendar event. We often use these for quick "catch up conference calls". You could use a Hangout to include someone in a meeting who was working from home or away at a conference.

Google Hangouts On Air are very different in that they are public, live-streamed and they are stored on YouTube. Regular Hangouts are like video conference calls, Hangouts on Air are more like broadcast TV.

The Hangout Process - It'll Be Alright On The Night

1. Start a Hangout ( check the On Air checkbox). You can do this from Google+, YouTube or even this link.
2. Choose your mic and camera settings (especially if you are using an additional webcam ).
3. Click Embed and copy the You Tube link ( see below )
4. Go to that YouTube link ( you can preview your stream ) other people will get a Broadcasting soon screen.
5. Share that link on Google+ or Twitter or via email ( if you want to ).
6. Important! Click Cameraman and set When someone joins, they should be: Hidden and muted From broadcast. This means you are in broadcast mode, rather than "massive meeting" mode where people can speak ( see below) .
7. Start your broadcast.




The End Result

We've found that the microphone on the laptop is more than adequate and the webcam is essential to easily framing the speaker. 

One week, the network dropping out meant that we had to use a uStream account to capture most of the seminar. This one disaster aside, it's been a complete success with academics joining in the Hangouts on a regular basis. 

We've created a Google Site to collect together the Seminar Hangouts here...  https://sites.google.com/a/york.ac.uk/yohrs/sharon-macdonald ... where we also add any presentation files.

Here's Sharon MacDonald giving the first Heritage Seminar of 2012-2013. 




The benefits of using Google Hangouts for quick collaborative video meetings or for public “on air” broadcasts are clear:

  • Everyone at York can participate, the don't need to download special apps, or register on various sites, they already have a log in. It works right now
  • The screen sharing tools work very well with your Google Apps. Think about using Google Slides rather than Powerpoint.
  • If your laptop has a camera, and even netbooks do nowadays, you don’t even need to buy a webcam ( we only did so for aesthetic reasons ).
  • Storage is huge.
  • And as with all our Google tools, it’s free

Using Google Spreadsheets to Record Chemistry Experiment Marks

Each year, around 200 chemistry students perform 20 Lab Experiments ( that’s roughly 4,000 a year ). Each test has a variety of marks to be kept by at least three people, the lab technician ( did they attend?), the tutor ( did they create the right chemistry and hand in their notes?) and the course leader ( are there any exceptions or mitigating circumstances etc).

What was previously a paper-based method had recently been made to work in our VLE, but the data captured was in a cumbersome wiki text format. And whilst the user interface was simple enough, the technology was struggling and getting the data collected from the VLE into our marks database required considerable human effort.

Working with David Pugh in Chemistry, we looked at using a Google Spreadsheets to collect the experiment data instead. After a few prototypes we have decided to use a very simple ( but quite wide ) spreadsheet to store the data and a web application “front end” for the markers to enter their marks.  I have worked with David, consulting about his requirements and have created him some Apps Script code. David is now editing the code, learning all about Apps Script and fine-tuning it to his needs. The ability to share small IT projects “in the cloud” using Apps Script is really empowering, for both David and I.

Whilst this project may at first glance seem a shade niche, but I often come across similar situations where technology has evolved and grown in the cracks between bigger systems.  The two systems here might be said to be “teaching” and the marks database ( SITS ).

It’s usually the case that these situations that the process ( or technology) requires a lot of upkeep and human input and that they don’t easily offer up accidental benefits, or usage that wasn’t envisaged when the original project was started. Now that we are taking control of our data in the “in between” stage, everyone is starting to see further possibilities of where this project might go next.

Implementation

After creating a prototype with the UI Builder, we decided that maybe a web application would be the best way forward. Both David and I are comfortable with simple HTML and we had an idea that we might need to use some of the excellent UI features of jQuery at some point.



Our web application had a very simple collection of screens, the Home Page (shown above) which leads onto a listing of students (not shown), each linked to a form with which markers could add the relevant student marks ( shown below ).




All the data is stored in a ridiculously simple spreadsheet. This was David's idea and significantly improved on my original design just because it essentially has one row per student, which hopefully will make later reporting or visualisation needs a breeze.


Specific tips/code/ideas that you can reuse


Keeping Your Code Tidy with a Single CSS file

In an attempt to keep our application tidy, we added this function and a file called css.html. The css.html file actually contains its own <style> tag.

function getCSS(){
 var template = HtmlService.createTemplateFromFile('css.html');
 return template.getRawContent()
}

What this means is that our four or five templates all begin with code like this and we only have one CSS file shared between them all. The jquery libraries were commented out but ready to be added back in should we need them. This made our templates cleaner and much easier to maintain.

<head>
<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/smoothness/jquery-ui.css" rel="Stylesheet" />
   <!--script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script-->
   <!--script type="text/javascript" src="http://code.jquery.com/ui/1.8.23/jquery-ui.min.js"></script-->
   <?!= getCSS() ?>
 </head>

Note the ! in the <?!= getCSS()?> It’s easy to miss. The exclamation means actually display the code contained and don’t escape it. 




Using Script Properties to store Spreadsheet IDs


We also found that adding a spreadsheet id to the Script Properties made it easier to maintain our code, with it appearing only in one place, rather than being repeatedly repeated.

function get_ss_id() {
 // Gets a spreadsheet ID for this project from File > Project properties.
 // See: https://developers.google.com/apps-script/class_scriptproperties#setProperty
 return ScriptProperties.getProperty('spreadsheet_id');
}

We can then use a line like this, rather than adding the spreadsheet id each time.

var ss = SpreadsheetApp.openById( get_ss_id() )


General and Probably Obvious Tips


It quickly became clear that we should keep spreadsheet code and web application code in separate files. We didn’t realise that, because of security reasons that I don't really understand, Apps Script can’t REDIRECT a HTTP request, which is an unusual limitation. 

Also, because you also don’t have any control over your application’s URLs we found that our doGet() function behaves a little like a controller in an MVC sense and so keeping that code free of functions that work with the data ( the model ) made it much easier to read and maintain.

Always write tests! Almost every function we wrote has a test function created for it just to check that it is working properly. This makes the use of the debugger and logger much more productive.


Rolling Your Own Security

I was quite surprised by the “all or nothing” security model with Googe Web Apps which seems a bit poor.  Unlike Google Apps where you can set the permission levels, adding people and groups, with Google Web Apps you can only choose ( Everyone, Everyone at York or Just Myself ) which seems a bit limited.

It was easy to create some code like that shown below but I was surprised that access controls to the web application couldn’t be set up in the familiar Sharing dialog.

function get_supervisors_emails(){
 
 var ss = SpreadsheetApp.openById('OUR_SUPERVISORS_SPREADSHEET_ID')
 var sheet = ss.getSheetByName("Supervisors");
 var values = sheet.getDataRange().getValues();
 supervisors = []
 for(i = 1; i < values.length ; i++){
   supervisor = values[i]
   email = supervisor[7]
   supervisors.push(email)
   
 }
 return supervisors
}


var user = Session.getUser( );
var email = user.getEmail( );
var supervisors = get_supervisors_emails()
 
 // Noddy security
 if ( supervisors.indexOf(email) == -1 ){
       var template = HtmlService.createTemplateFromFile('NotSupervisorError.html');
       // This is used by the "<?= action ?> tag in the template
       template.action = ScriptApp.getService().getUrl( );
       template.email = email
       return template.evaluate();
 }



Conclusions


The project is now at the point where David is learning how it works, asking questions and making changes. Like me, David isn’t a programmer but is comfortable with simple HTML and Javascript. The current stage will be all about making the application work as easily as possible for the markers but David already has his eye on the next developments.

For example, the department needs to gather attendance data for immigration compliance, they will be able to show a marker’s average mark, they will be able to show students “falling behind” and integrate all of this into a “Lab Experiments Dashboard” showing key data items as visualisations. Watch this space for these developments.

Looking back, maybe we should have used Fusion Tables instead of Google Spreadsheets because our spreadsheet has grown quite large. I don’t think we will bump up against the cell limits that Google Spreadsheets have but we may have a use for the SQL-like means of querying our data.

The key benefit of this project will be about a department taking control over their data and complex processes and making them less arduous. Less time will be spent moving data from one area to another, there will be fewer human errors, and the data collected will be more “audit-friendly” since we log who edits it. But the part of the story I find most interesting is the new opportunities to better understand their own data, and ultimately to provide a better service to students.





Using Hangouts On Air To Present In Kings Manor, York and Southampton



Last night, Alice Watterson (Glasgow School of Art) presented Digital Dwelling at Skara Brae to a packed room K/111, Kings Manor, University of York about her collaboration making digital reconstructions. Here she is (above ) from the "stage" with Sara Perry.



Alice was also watched by a roomful of researchers at Southampton University, who tuned in to the Hangout and managed to ask questions in a short Q&A at the end.

We're still experimenting with how to get the best from Google+ Hangouts whilst keeping the recording process speaker-responsibility-free and technology-minimalist in that we don't want the speaker to have to do anything special to be streamed, and we also don't want Sara, who hosts the seminars to have to set up heaps of scary equipment.

We may have to compromise though. For example, if the speaker would be willing to send us their slides beforehand and willing, instead of using Powerpoint - to use the Hangout tool, then external viewers could watch the slides rather than the webcam image. This would make for a much better recording and improve the experience for external viewers but opens up the potential for all sorts of problems. Still, given a willing presenter, it will be worth a try. Maybe next time.

Alice's presentation and slides are online here:






Monday, 28 January 2013

Using Google Sites For Student Work ( Philosophy )

The Idea

Prof +Tom Stoneham and Nick Jones had the idea of using Google Sites as an alternative to textual documents for student work, in this case, a dissertation about a certain philosopher.

Google Sites give the opportunity for the creation of a network of information rather than a narrative document. A site can hold videos, audio and refer to other online resources with links.

The idea was that there would be a simple template site (see above), with boiler plate text and guidance about copyright issues etc. and the student could then start editing existing pages and creating new ones.

Administration

From an administrational perspective, the Google sites would need to be closed to student when the deadline was met. Ideally, it would good if the student could have a copy of their Google Site - both to continue working on it and to use in their portfolio of work.

Whilst Tom didn't need the student's identity to be anonymized, but we used a unique reference number for the name of the site anyway.

Technical

From a technical perspective, creating a 80 Google Sites would seem to be a simple task. It would be, were it not for the fact that often, when creating resources like Google Sites or Calendars or Groups there can be a variable lag from asking Google to create the resource and it being available for further use. And whilst there is a lag, the Apps Script code you write to create a Google site is asynchronous, which effectively means you do the coding equivalent of ...

var basket = new Basket()
basket.addEggs( 12 ) // At this point there probably is NO BASKET! Error!

The "solution" I hit upon was using a once-a-minute Trigger to pick off the tasks I wanted to run one at time, meaning that after each one Google's system have more than enough time to "catch up". The tasks being...

  • create a Google Site from a template
  • add the Student as Editor
  • When the deadline is reached, add the examiner to the site as Viewer and remove the student as Editor
  • Email the examiner that they have a site to mark
  • create a copy of the student's Google Site and make them Owner for their portfolio.
I created a spreadsheet with three sheets ( to the wind ). The first, Administration is where you set up the name of your project, which Google Site to use as your template and when the deadline is. The second, Students is a list of student emails, unique references and examiner emails. And the last is a utility sheet that gets a list of your Google Sites ( for interface niceness ).

I then created a huge function to gently create the Sites and add the right permissions to the right people at the right time. It looks like this.





function triggered_site_maker( ){
 
  var ss = SpreadsheetApp.getActiveSpreadsheet( );
  var sheet = ss.getSheetByName( "Students" );
 
  // Get default values from the Administration sheet
  var admin_sheet =  ss.getSheetByName("Administration");
  var template_site_name =  admin_sheet.getRange("B1").getValues()
  var domain =  admin_sheet.getRange("B4").getValue( )
  var homework_id =  admin_sheet.getRange("B5").getValues( )
  var homework_title =  admin_sheet.getRange("B6").getValues( )
 
  // get all the data in the Students sheet
  var range = sheet.getDataRange().getValues();
  var students = rangeToObjects(range);
 
  for(var i = 0; i < students.length; i++){  
    var student = students[i];
   
    var student_unique_ref = student.uniquereference.toString().toLowerCase()
    var new_site_name = homework_id + "-" + student_unique_ref  
   
    var student_email = student.email
    var examiner = student.examiner
    var siteurl = student.siteurl
    var status = student.status
   
    var statuscellname = "E" + (i +2 )
    var timestampcellname = "F" + ( i +2 )
    var timestamp =new Date()
    var cellname = "D" + ( i +2 ) //
   
    if ( student_unique_ref != ''){
     
      switch (status){
        case '': // We're at the beginning, make a site for the student.
         
          try{
            // Note: sites_url is a NEW sites url not an already made one.
            var sites_url = create_a_site(template_site_name, new_site_name )
           
            sheet.getRange(cellname).setValue(sites_url)
            sheet.getRange(statuscellname).setValue('site made')
            sheet.getRange( timestampcellname ).setValue( timestamp )
            break
           
          }catch(e){
            ErrorMail(e)
          }
         
        case 'site made': // The site has been made, now add the student.
         
          try{
            var name = SiteUrlToName(siteurl) // workout site's name from URL. Had problems with getSiteByUrl()
           
            if (domain == ''){
              var site = SitesApp.getSite( name)
              }else{
                var site = SitesApp.getSite(domain, name)
             }
            site.addEditor(student_email);
            sheet.getRange(statuscellname).setValue('site made and student added')
         
            site.setTitle( homework_title ) // Handy for searching later I guess. Gulp.
            sheet.getRange( timestampcellname ).setValue( timestamp )
           
          }catch (e) {
            ErrorMail(e)
          }
          break
         
          // NOTIFY EXAMINER/REMOVE STUDENT AND COPY SITE  
          case  'site made and student added':
         
          var deadline =  admin_sheet.getRange("B2").getValue( )
          var now = new Date()
          if (now > deadline){
            try{
              var name = SiteUrlToName(siteurl) // workout site's name from URL
              if (domain == ''){
                var site = SitesApp.getSite( name)
                }else{
                  var site = SitesApp.getSite(domain, name)
                  }
              site.removeEditor( student_email )
              sheet.getRange(statuscellname).setValue('student removed')
              sheet.getRange( timestampcellname ).setValue( timestamp )
             
            }catch(e){
              ErrorMail(e)
            }
          }
          break
         
          case  'student removed': //Now, add the examiner
         
          try{
            var name = SiteUrlToName(siteurl) // workout site's name from URL
            if (domain == ''){
              var site = SitesApp.getSite( name)
              }else{
                var site = SitesApp.getSite(domain, name)
                }
            var site_name = site.getName()
            Logger.log( site_name)
            var viewers = site.getViewers( )
            if ( examiner != ''){
             
              site.addViewer( examiner )
              sheet.getRange(statuscellname).setValue('examiner added')
              sheet.getRange( timestampcellname ).setValue( timestamp )
            }
            break
           
          }catch(e){
            ErrorMail(e)
          }
         
        case  'examiner added': //Email them with a link to the site
          try{
            MailApp.sendEmail(examiner,
                              "A Google Site To Mark",
                              "As an examiner, a Google Site called '" + homework_title +"' has been created for you to mark. \n\n " +
                              "Unique student reference: " + student_unique_ref + "\n\n " +
                              "url: " + siteurl + "\n\n",                  
                              {name:"Google Site Marking", noReply:true});
            sheet.getRange(statuscellname).setValue('examiner emailed')
            sheet.getRange( timestampcellname ).setValue( timestamp )
            break
          }catch(e){
            ErrorMail(e)
          }
         
        case  'examiner emailed': //Make the student a copy for their portfolio.
          try{
           
            var name = SiteUrlToName(siteurl) // workout site'ss name from URL
            if (domain == ''){
              var site = SitesApp.getSite( name)
              }else{
                var site = SitesApp.getSite(domain, name)
                }
            var new_name = "" + name + "-copy"
            var title = site.getTitle()
            var summary = site.getSummary()
            var site_name = site.getName( ) // use this site as a template
           
            if (domain == ''){
              var copied_site = SitesApp.copySite(  new_name , title, summary, name)
              }else{
                var copied_site = SitesApp.copySite( domain, new_name , title, summary, name)
                }
           
            Utilities.sleep(4000)
            var copied_site_url = copied_site.getUrl()
            sheet.getRange( "I" + (i + 2) ).setValue(  )
            sheet.getRange(statuscellname).setValue('student copy created')
            sheet.getRange( timestampcellname ).setValue( timestamp )
            break
           
          }catch(e){
            ErrorMail(e)
          }
         
         
        case  'student copy created': // Add them as owners to these copies.
          try{
            var copied_url = sheet.getRange( "I" + (i + 2) ).getValue()
           
            var name = SiteUrlToName(siteurl)
            if (domain == ''){
              var site = SitesApp.getSite( name)
              }else{
                var site = SitesApp.getSite(domain, name)
                }
            site.addOwner(student_email)
            sheet.getRange(statuscellname).setValue('student added to copy')
            sheet.getRange( timestampcellname ).setValue( timestamp )
            break
           
          }catch(e){
            ErrorMail(e)
          }
        case  'student added to copy':
         
          sheet.getRange(statuscellname).setValue('Completed')
          sheet.getRange( timestampcellname ).setValue( timestamp )
          break
      }//end case
     
     
    }//end if empty student_unique_ref -- 
  }//end forloop
 
  // It would be good at this point if we could then de-activate the trigger....
 
}

function test_trigger(){
  triggered_site_maker()
}





function create_a_site( template_site_name, new_site_name ){

  var ss = SpreadsheetApp.getActiveSpreadsheet( );
  var admin_sheet =  ss.getSheetByName("Administration");
  var domain =  admin_sheet.getRange("B4").getValues( )
 
  if ( domain == ''){
    var template_site = SitesApp.getSite( template_site_name )
    }else{
      var template_site = SitesApp.getSite(domain, template_site_name );
    }
 
  var title = template_site.getTitle()
  var summary = template_site.getSummary()
 
  // See the warning in https://developers.google.com/apps-script/class_sitesapp
  if ( domain == ''){
    var site = SitesApp.copySite(  new_site_name, title, summary, template_site);
  }else{
    var site = SitesApp.copySite( domain, new_site_name, title, summary, template_site);
  }
  Utilities.sleep( 3000 ) // Yawn! 
  var sites_url = site.getUrl( );
  return sites_url ;
}





function my_sites_names(){   /////  GET A LIST OF SITES YOU'VE MADE 
  // This is used in the interface as a data validation thing.
  var ss = SpreadsheetApp.getActiveSpreadsheet( );
  var admin_sheet =  ss.getSheetByName("Administration");
  var my_sites_sheet =  ss.getSheetByName("My Sites");
 
  // clear the ole data...
  var last_row = my_sites_sheet.getDataRange().getLastRow()
  var range = my_sites_sheet.getRange(2, 2, last_row)
  range.clear()
 
  var domain =  admin_sheet.getRange("B4").getValue( )
 
  if (domain == ''){
    var sites = SitesApp.getSites()
    }else{
      var sites = SitesApp.getSites(domain)
      }
 
  var site_names = new Array();
  for(var i = 0; i < sites.length; i++){
    var site = sites[i]
    var site_name = site.getName()
    //set name
    var range = my_sites_sheet.getRange(i+2, 1)
    range.setValue( site_name )
    Logger.log( site_name )
   
    //set URL
    var range = my_sites_sheet.getRange(i+2, 3)
    range.setValue( site.getUrl() )
   
    site_names.push(  site.getName() )
  }
  return site_names
}



Take a Copy And Try It Yourself

Note: This will only work with Apps for Education/Business accounts ( and won't work for consumer accounts because you can't create a Google Site for random people willy nilly ).


1. File > Make a copy of the spreadsheet here.

2. Go to the Script Editor and Run Event Handling > onOpen() . This authenticates the Script to ask which are your Google Sites. This is just for interface niceness.

3. Fill in the Administration sheet. This needs a domain and a deadline. There's also a "project ID" which can be anything, but is used to differentiate different "assignments".

4. Add students, unique references and examiners to the Students sheet ( there is some Utility code to help generate them if you don't have unique IDs ).

5. You might want to add your email address to the Utility > ErrorEmail function. When a Script is running from a Trigger I don't think you get access to any Logger.log() messages, so this is a handy way of debugging the app.

6. You are now ready to run the triggered_site_maker() function. You can run it straight from the ScriptEditor, but you will need to add it as a Trigger for it to do all it needs to do. Like this...




Once running, you should see various values being populated as your Google Sites get created and permissions added. When the deadline is met, the students are removed as Editors and given a copy of their site. Once this is finished you can delete the Trigger.